/*------------------------------------------------------------------------- * * jsonb.c * I/O routines for jsonb type * * Copyright (c) 2014-2023, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/utils/adt/jsonb.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "access/transam.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/datetime.h" #include "utils/json.h" #include "utils/jsonb.h" #include "utils/jsonfuncs.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/typcache.h" typedef struct JsonbInState { JsonbParseState *parseState; JsonbValue *res; Node *escontext; } JsonbInState; /* unlike with json categories, we need to treat json and jsonb differently */ typedef enum /* type categories for datum_to_jsonb */ { JSONBTYPE_NULL, /* null, so we didn't bother to identify */ JSONBTYPE_BOOL, /* boolean (built-in types only) */ JSONBTYPE_NUMERIC, /* numeric (ditto) */ JSONBTYPE_DATE, /* we use special formatting for datetimes */ JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ JSONBTYPE_JSON, /* JSON */ JSONBTYPE_JSONB, /* JSONB */ JSONBTYPE_ARRAY, /* array */ JSONBTYPE_COMPOSITE, /* composite */ JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ JSONBTYPE_OTHER /* all else */ } JsonbTypeCategory; typedef struct JsonbAggState { JsonbInState *res; JsonbTypeCategory key_category; Oid key_output_func; JsonbTypeCategory val_category; Oid val_output_func; } JsonbAggState; static inline Datum jsonb_from_cstring(char *json, int len, Node *escontext); static bool checkStringLen(size_t len, Node *escontext); static JsonParseErrorType jsonb_in_object_start(void *pstate); static JsonParseErrorType jsonb_in_object_end(void *pstate); static JsonParseErrorType jsonb_in_array_start(void *pstate); static JsonParseErrorType jsonb_in_array_end(void *pstate); static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); static void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid); static void composite_to_jsonb(Datum composite, JsonbInState *result); static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonbTypeCategory tcategory, Oid outfuncoid); static void array_to_jsonb_internal(Datum array, JsonbInState *result); static void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid); static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar); static void add_jsonb(Datum val, bool is_null, JsonbInState *result, Oid val_type, bool key_scalar); static JsonbParseState *clone_parse_state(JsonbParseState *state); static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); static void add_indent(StringInfo out, bool indent, int level); /* * jsonb type input function */ Datum jsonb_in(PG_FUNCTION_ARGS) { char *json = PG_GETARG_CSTRING(0); return jsonb_from_cstring(json, strlen(json), fcinfo->context); } /* * jsonb type recv function * * The type is sent as text in binary mode, so this is almost the same * as the input function, but it's prefixed with a version number so we * can change the binary format sent in future if necessary. For now, * only version 1 is supported. */ Datum jsonb_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); int version = pq_getmsgint(buf, 1); char *str; int nbytes; if (version == 1) str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); else elog(ERROR, "unsupported jsonb version number %d", version); return jsonb_from_cstring(str, nbytes, NULL); } /* * jsonb type output function */ Datum jsonb_out(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); char *out; out = JsonbToCString(NULL, &jb->root, VARSIZE(jb)); PG_RETURN_CSTRING(out); } /* * jsonb type send function * * Just send jsonb as a version number, then a string of text */ Datum jsonb_send(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); StringInfoData buf; StringInfo jtext = makeStringInfo(); int version = 1; (void) JsonbToCString(jtext, &jb->root, VARSIZE(jb)); pq_begintypsend(&buf); pq_sendint8(&buf, version); pq_sendtext(&buf, jtext->data, jtext->len); pfree(jtext->data); pfree(jtext); PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } /* * Get the type name of a jsonb container. */ static const char * JsonbContainerTypeName(JsonbContainer *jbc) { JsonbValue scalar; if (JsonbExtractScalar(jbc, &scalar)) return JsonbTypeName(&scalar); else if (JsonContainerIsArray(jbc)) return "array"; else if (JsonContainerIsObject(jbc)) return "object"; else { elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header); return "unknown"; } } /* * Get the type name of a jsonb value. */ const char * JsonbTypeName(JsonbValue *val) { switch (val->type) { case jbvBinary: return JsonbContainerTypeName(val->val.binary.data); case jbvObject: return "object"; case jbvArray: return "array"; case jbvNumeric: return "number"; case jbvString: return "string"; case jbvBool: return "boolean"; case jbvNull: return "null"; case jbvDatetime: switch (val->val.datetime.typid) { case DATEOID: return "date"; case TIMEOID: return "time without time zone"; case TIMETZOID: return "time with time zone"; case TIMESTAMPOID: return "timestamp without time zone"; case TIMESTAMPTZOID: return "timestamp with time zone"; default: elog(ERROR, "unrecognized jsonb value datetime type: %d", val->val.datetime.typid); } return "unknown"; default: elog(ERROR, "unrecognized jsonb value type: %d", val->type); return "unknown"; } } /* * SQL function jsonb_typeof(jsonb) -> text * * This function is here because the analog json function is in json.c, since * it uses the json parser internals not exposed elsewhere. */ Datum jsonb_typeof(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); const char *result = JsonbContainerTypeName(&in->root); PG_RETURN_TEXT_P(cstring_to_text(result)); } /* * jsonb_from_cstring * * Turns json string into a jsonb Datum. * * Uses the json parser (with hooks) to construct a jsonb. * * If escontext points to an ErrorSaveContext, errors are reported there * instead of being thrown. */ static inline Datum jsonb_from_cstring(char *json, int len, Node *escontext) { JsonLexContext *lex; JsonbInState state; JsonSemAction sem; memset(&state, 0, sizeof(state)); memset(&sem, 0, sizeof(sem)); lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); state.escontext = escontext; sem.semstate = (void *) &state; sem.object_start = jsonb_in_object_start; sem.array_start = jsonb_in_array_start; sem.object_end = jsonb_in_object_end; sem.array_end = jsonb_in_array_end; sem.scalar = jsonb_in_scalar; sem.object_field_start = jsonb_in_object_field_start; if (!pg_parse_json_or_errsave(lex, &sem, escontext)) return (Datum) 0; /* after parsing, the item member has the composed jsonb structure */ PG_RETURN_POINTER(JsonbValueToJsonb(state.res)); } static bool checkStringLen(size_t len, Node *escontext) { if (len > JENTRY_OFFLENMASK) ereturn(escontext, false, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("string too long to represent as jsonb string"), errdetail("Due to an implementation restriction, jsonb strings cannot exceed %d bytes.", JENTRY_OFFLENMASK))); return true; } static JsonParseErrorType jsonb_in_object_start(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); return JSON_SUCCESS; } static JsonParseErrorType jsonb_in_object_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL); return JSON_SUCCESS; } static JsonParseErrorType jsonb_in_array_start(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL); return JSON_SUCCESS; } static JsonParseErrorType jsonb_in_array_end(void *pstate) { JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); return JSON_SUCCESS; } static JsonParseErrorType jsonb_in_object_field_start(void *pstate, char *fname, bool isnull) { JsonbInState *_state = (JsonbInState *) pstate; JsonbValue v; Assert(fname != NULL); v.type = jbvString; v.val.string.len = strlen(fname); if (!checkStringLen(v.val.string.len, _state->escontext)) return JSON_SEM_ACTION_FAILED; v.val.string.val = fname; _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); return JSON_SUCCESS; } static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal) { switch (scalarVal->type) { case jbvNull: appendBinaryStringInfo(out, "null", 4); break; case jbvString: escape_json(out, pnstrdup(scalarVal->val.string.val, scalarVal->val.string.len)); break; case jbvNumeric: appendStringInfoString(out, DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(scalarVal->val.numeric)))); break; case jbvBool: if (scalarVal->val.boolean) appendBinaryStringInfo(out, "true", 4); else appendBinaryStringInfo(out, "false", 5); break; default: elog(ERROR, "unknown jsonb scalar type"); } } /* * For jsonb we always want the de-escaped value - that's what's in token */ static JsonParseErrorType jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype) { JsonbInState *_state = (JsonbInState *) pstate; JsonbValue v; Datum numd; switch (tokentype) { case JSON_TOKEN_STRING: Assert(token != NULL); v.type = jbvString; v.val.string.len = strlen(token); if (!checkStringLen(v.val.string.len, _state->escontext)) return JSON_SEM_ACTION_FAILED; v.val.string.val = token; break; case JSON_TOKEN_NUMBER: /* * No need to check size of numeric values, because maximum * numeric size is well below the JsonbValue restriction */ Assert(token != NULL); v.type = jbvNumeric; if (!DirectInputFunctionCallSafe(numeric_in, token, InvalidOid, -1, _state->escontext, &numd)) return JSON_SEM_ACTION_FAILED; v.val.numeric = DatumGetNumeric(numd); break; case JSON_TOKEN_TRUE: v.type = jbvBool; v.val.boolean = true; break; case JSON_TOKEN_FALSE: v.type = jbvBool; v.val.boolean = false; break; case JSON_TOKEN_NULL: v.type = jbvNull; break; default: /* should not be possible */ elog(ERROR, "invalid json token type"); break; } if (_state->parseState == NULL) { /* single scalar */ JsonbValue va; va.type = jbvArray; va.val.array.rawScalar = true; va.val.array.nElems = 1; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, &va); _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); } else { JsonbValue *o = &_state->parseState->contVal; switch (o->type) { case jbvArray: _state->res = pushJsonbValue(&_state->parseState, WJB_ELEM, &v); break; case jbvObject: _state->res = pushJsonbValue(&_state->parseState, WJB_VALUE, &v); break; default: elog(ERROR, "unexpected parent of nested structure"); } } return JSON_SUCCESS; } /* * JsonbToCString * Converts jsonb value to a C-string. * * If 'out' argument is non-null, the resulting C-string is stored inside the * StringBuffer. The resulting string is always returned. * * A typical case for passing the StringInfo in rather than NULL is where the * caller wants access to the len attribute without having to call strlen, e.g. * if they are converting it to a text* object. */ char * JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len) { return JsonbToCStringWorker(out, in, estimated_len, false); } /* * same thing but with indentation turned on */ char * JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len) { return JsonbToCStringWorker(out, in, estimated_len, true); } /* * common worker for above two functions */ static char * JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent) { bool first = true; JsonbIterator *it; JsonbValue v; JsonbIteratorToken type = WJB_DONE; int level = 0; bool redo_switch = false; /* If we are indenting, don't add a space after a comma */ int ispaces = indent ? 1 : 2; /* * Don't indent the very first item. This gets set to the indent flag at * the bottom of the loop. */ bool use_indent = false; bool raw_scalar = false; bool last_was_key = false; if (out == NULL) out = makeStringInfo(); enlargeStringInfo(out, (estimated_len >= 0) ? estimated_len : 64); it = JsonbIteratorInit(in); while (redo_switch || ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)) { redo_switch = false; switch (type) { case WJB_BEGIN_ARRAY: if (!first) appendBinaryStringInfo(out, ", ", ispaces); if (!v.val.array.rawScalar) { add_indent(out, use_indent && !last_was_key, level); appendStringInfoCharMacro(out, '['); } else raw_scalar = true; first = true; level++; break; case WJB_BEGIN_OBJECT: if (!first) appendBinaryStringInfo(out, ", ", ispaces); add_indent(out, use_indent && !last_was_key, level); appendStringInfoCharMacro(out, '{'); first = true; level++; break; case WJB_KEY: if (!first) appendBinaryStringInfo(out, ", ", ispaces); first = true; add_indent(out, use_indent, level); /* json rules guarantee this is a string */ jsonb_put_escaped_value(out, &v); appendBinaryStringInfo(out, ": ", 2); type = JsonbIteratorNext(&it, &v, false); if (type == WJB_VALUE) { first = false; jsonb_put_escaped_value(out, &v); } else { Assert(type == WJB_BEGIN_OBJECT || type == WJB_BEGIN_ARRAY); /* * We need to rerun the current switch() since we need to * output the object which we just got from the iterator * before calling the iterator again. */ redo_switch = true; } break; case WJB_ELEM: if (!first) appendBinaryStringInfo(out, ", ", ispaces); first = false; if (!raw_scalar) add_indent(out, use_indent, level); jsonb_put_escaped_value(out, &v); break; case WJB_END_ARRAY: level--; if (!raw_scalar) { add_indent(out, use_indent, level); appendStringInfoCharMacro(out, ']'); } first = false; break; case WJB_END_OBJECT: level--; add_indent(out, use_indent, level); appendStringInfoCharMacro(out, '}'); first = false; break; default: elog(ERROR, "unknown jsonb iterator token type"); } use_indent = indent; last_was_key = redo_switch; } Assert(level == 0); return out->data; } static void add_indent(StringInfo out, bool indent, int level) { if (indent) { appendStringInfoCharMacro(out, '\n'); appendStringInfoSpaces(out, level * 4); } } /* * Determine how we want to render values of a given type in datum_to_jsonb. * * Given the datatype OID, return its JsonbTypeCategory, as well as the type's * output function OID. If the returned category is JSONBTYPE_JSONCAST, * we return the OID of the relevant cast function instead. */ static void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid) { bool typisvarlena; /* Look through any domain */ typoid = getBaseType(typoid); *outfuncoid = InvalidOid; /* * We need to get the output function for everything except date and * timestamp types, booleans, array and composite types, json and jsonb, * and non-builtin types where there's a cast to json. In this last case * we return the oid of the cast function instead. */ switch (typoid) { case BOOLOID: *tcategory = JSONBTYPE_BOOL; break; case INT2OID: case INT4OID: case INT8OID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); *tcategory = JSONBTYPE_NUMERIC; break; case DATEOID: *tcategory = JSONBTYPE_DATE; break; case TIMESTAMPOID: *tcategory = JSONBTYPE_TIMESTAMP; break; case TIMESTAMPTZOID: *tcategory = JSONBTYPE_TIMESTAMPTZ; break; case JSONBOID: *tcategory = JSONBTYPE_JSONB; break; case JSONOID: *tcategory = JSONBTYPE_JSON; break; default: /* Check for arrays and composites */ if (OidIsValid(get_element_type(typoid)) || typoid == ANYARRAYOID || typoid == ANYCOMPATIBLEARRAYOID || typoid == RECORDARRAYOID) *tcategory = JSONBTYPE_ARRAY; else if (type_is_rowtype(typoid)) /* includes RECORDOID */ *tcategory = JSONBTYPE_COMPOSITE; else { /* It's probably the general case ... */ *tcategory = JSONBTYPE_OTHER; /* * but first let's look for a cast to json (note: not to * jsonb) if it's not built-in. */ if (typoid >= FirstNormalObjectId) { Oid castfunc; CoercionPathType ctype; ctype = find_coercion_pathway(JSONOID, typoid, COERCION_EXPLICIT, &castfunc); if (ctype == COERCION_PATH_FUNC && OidIsValid(castfunc)) { *tcategory = JSONBTYPE_JSONCAST; *outfuncoid = castfunc; } else { /* not a cast type, so just get the usual output func */ getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); } } else { /* any other builtin type */ getTypeOutputInfo(typoid, outfuncoid, &typisvarlena); } break; } } } /* * Turn a Datum into jsonb, adding it to the result JsonbInState. * * tcategory and outfuncoid are from a previous call to json_categorize_type, * except that if is_null is true then they can be invalid. * * If key_scalar is true, the value is stored as a key, so insist * it's of an acceptable type, and force it to be a jbvString. * * Note: currently, we assume that result->escontext is NULL and errors * will be thrown. */ static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar) { char *outputstr; bool numeric_error; JsonbValue jb; bool scalar_jsonb = false; check_stack_depth(); /* Convert val to a JsonbValue in jb (in most cases) */ if (is_null) { Assert(!key_scalar); jb.type = jbvNull; } else if (key_scalar && (tcategory == JSONBTYPE_ARRAY || tcategory == JSONBTYPE_COMPOSITE || tcategory == JSONBTYPE_JSON || tcategory == JSONBTYPE_JSONB || tcategory == JSONBTYPE_JSONCAST)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("key value must be scalar, not array, composite, or json"))); } else { if (tcategory == JSONBTYPE_JSONCAST) val = OidFunctionCall1(outfuncoid, val); switch (tcategory) { case JSONBTYPE_ARRAY: array_to_jsonb_internal(val, result); break; case JSONBTYPE_COMPOSITE: composite_to_jsonb(val, result); break; case JSONBTYPE_BOOL: if (key_scalar) { outputstr = DatumGetBool(val) ? "true" : "false"; jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { jb.type = jbvBool; jb.val.boolean = DatumGetBool(val); } break; case JSONBTYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); if (key_scalar) { /* always quote keys */ jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } else { /* * Make it numeric if it's a valid JSON number, otherwise * a string. Invalid numeric output will always have an * 'N' or 'n' in it (I think). */ numeric_error = (strchr(outputstr, 'N') != NULL || strchr(outputstr, 'n') != NULL); if (!numeric_error) { Datum numd; jb.type = jbvNumeric; numd = DirectFunctionCall3(numeric_in, CStringGetDatum(outputstr), ObjectIdGetDatum(InvalidOid), Int32GetDatum(-1)); jb.val.numeric = DatumGetNumeric(numd); pfree(outputstr); } else { jb.type = jbvString; jb.val.string.len = strlen(outputstr); jb.val.string.val = outputstr; } } break; case JSONBTYPE_DATE: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_TIMESTAMP: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_TIMESTAMPTZ: jb.type = jbvString; jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_JSONCAST: case JSONBTYPE_JSON: { /* parse the json right into the existing result object */ JsonLexContext *lex; JsonSemAction sem; text *json = DatumGetTextPP(val); lex = makeJsonLexContext(json, true); memset(&sem, 0, sizeof(sem)); sem.semstate = (void *) result; sem.object_start = jsonb_in_object_start; sem.array_start = jsonb_in_array_start; sem.object_end = jsonb_in_object_end; sem.array_end = jsonb_in_array_end; sem.scalar = jsonb_in_scalar; sem.object_field_start = jsonb_in_object_field_start; pg_parse_json_or_ereport(lex, &sem); } break; case JSONBTYPE_JSONB: { Jsonb *jsonb = DatumGetJsonbP(val); JsonbIterator *it; it = JsonbIteratorInit(&jsonb->root); if (JB_ROOT_IS_SCALAR(jsonb)) { (void) JsonbIteratorNext(&it, &jb, true); Assert(jb.type == jbvArray); (void) JsonbIteratorNext(&it, &jb, true); scalar_jsonb = true; } else { JsonbIteratorToken type; while ((type = JsonbIteratorNext(&it, &jb, false)) != WJB_DONE) { if (type == WJB_END_ARRAY || type == WJB_END_OBJECT || type == WJB_BEGIN_ARRAY || type == WJB_BEGIN_OBJECT) result->res = pushJsonbValue(&result->parseState, type, NULL); else result->res = pushJsonbValue(&result->parseState, type, &jb); } } } break; default: outputstr = OidOutputFunctionCall(outfuncoid, val); jb.type = jbvString; jb.val.string.len = strlen(outputstr); (void) checkStringLen(jb.val.string.len, NULL); jb.val.string.val = outputstr; break; } } /* Now insert jb into result, unless we did it recursively */ if (!is_null && !scalar_jsonb && tcategory >= JSONBTYPE_JSON && tcategory <= JSONBTYPE_JSONCAST) { /* work has been done recursively */ return; } else if (result->parseState == NULL) { /* single root scalar */ JsonbValue va; va.type = jbvArray; va.val.array.rawScalar = true; va.val.array.nElems = 1; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, &va); result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); } else { JsonbValue *o = &result->parseState->contVal; switch (o->type) { case jbvArray: result->res = pushJsonbValue(&result->parseState, WJB_ELEM, &jb); break; case jbvObject: result->res = pushJsonbValue(&result->parseState, key_scalar ? WJB_KEY : WJB_VALUE, &jb); break; default: elog(ERROR, "unexpected parent of nested structure"); } } } /* * Process a single dimension of an array. * If it's the innermost dimension, output the values, otherwise call * ourselves recursively to process the next dimension. */ static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonbTypeCategory tcategory, Oid outfuncoid) { int i; Assert(dim < ndims); result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); for (i = 1; i <= dims[dim]; i++) { if (dim + 1 == ndims) { datum_to_jsonb(vals[*valcount], nulls[*valcount], result, tcategory, outfuncoid, false); (*valcount)++; } else { array_dim_to_jsonb(result, dim + 1, ndims, dims, vals, nulls, valcount, tcategory, outfuncoid); } } result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); } /* * Turn an array into JSON. */ static void array_to_jsonb_internal(Datum array, JsonbInState *result) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); int *dim; int ndim; int nitems; int count = 0; Datum *elements; bool *nulls; int16 typlen; bool typbyval; char typalign; JsonbTypeCategory tcategory; Oid outfuncoid; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); nitems = ArrayGetNItems(ndim, dim); if (nitems <= 0) { result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); return; } get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); jsonb_categorize_type(element_type, &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); array_dim_to_jsonb(result, 0, ndim, dim, elements, nulls, &count, tcategory, outfuncoid); pfree(elements); pfree(nulls); } /* * Turn a composite / record into JSON. */ static void composite_to_jsonb(Datum composite, JsonbInState *result) { HeapTupleHeader td; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tmptup, *tuple; int i; td = DatumGetHeapTupleHeader(composite); /* Extract rowtype info and find a tupdesc */ tupType = HeapTupleHeaderGetTypeId(td); tupTypmod = HeapTupleHeaderGetTypMod(td); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); /* Build a temporary HeapTuple control structure */ tmptup.t_len = HeapTupleHeaderGetDatumLength(td); tmptup.t_data = td; tuple = &tmptup; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); for (i = 0; i < tupdesc->natts; i++) { Datum val; bool isnull; char *attname; JsonbTypeCategory tcategory; Oid outfuncoid; JsonbValue v; Form_pg_attribute att = TupleDescAttr(tupdesc, i); if (att->attisdropped) continue; attname = NameStr(att->attname); v.type = jbvString; /* don't need checkStringLen here - can't exceed maximum name length */ v.val.string.len = strlen(attname); v.val.string.val = attname; result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); val = heap_getattr(tuple, i + 1, tupdesc, &isnull); if (isnull) { tcategory = JSONBTYPE_NULL; outfuncoid = InvalidOid; } else jsonb_categorize_type(att->atttypid, &tcategory, &outfuncoid); datum_to_jsonb(val, isnull, result, tcategory, outfuncoid, false); } result->res = pushJsonbValue(&result->parseState, WJB_END_OBJECT, NULL); ReleaseTupleDesc(tupdesc); } /* * Append JSON text for "val" to "result". * * This is just a thin wrapper around datum_to_jsonb. If the same type will be * printed many times, avoid using this; better to do the jsonb_categorize_type * lookups only once. */ static void add_jsonb(Datum val, bool is_null, JsonbInState *result, Oid val_type, bool key_scalar) { JsonbTypeCategory tcategory; Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); if (is_null) { tcategory = JSONBTYPE_NULL; outfuncoid = InvalidOid; } else jsonb_categorize_type(val_type, &tcategory, &outfuncoid); datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } /* * Is the given type immutable when coming out of a JSONB context? * * At present, datetimes are all considered mutable, because they * depend on timezone. XXX we should also drill down into objects and * arrays, but do not. */ bool to_jsonb_is_immutable(Oid typoid) { JsonbTypeCategory tcategory; Oid outfuncoid; jsonb_categorize_type(typoid, &tcategory, &outfuncoid); switch (tcategory) { case JSONBTYPE_NULL: case JSONBTYPE_BOOL: case JSONBTYPE_JSON: case JSONBTYPE_JSONB: return true; case JSONBTYPE_DATE: case JSONBTYPE_TIMESTAMP: case JSONBTYPE_TIMESTAMPTZ: return false; case JSONBTYPE_ARRAY: return false; /* TODO recurse into elements */ case JSONBTYPE_COMPOSITE: return false; /* TODO recurse into fields */ case JSONBTYPE_NUMERIC: case JSONBTYPE_JSONCAST: case JSONBTYPE_OTHER: return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; } return false; /* not reached */ } /* * SQL function to_jsonb(anyvalue) */ Datum to_jsonb(PG_FUNCTION_ARGS) { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); JsonbInState result; JsonbTypeCategory tcategory; Oid outfuncoid; if (val_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); jsonb_categorize_type(val_type, &tcategory, &outfuncoid); memset(&result, 0, sizeof(JsonbInState)); datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, bool absent_on_null, bool unique_keys) { int i; JsonbInState result; if (nargs % 2 != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument list must have even number of elements"), /* translator: %s is a SQL function name */ errhint("The arguments of %s must consist of alternating keys and values.", "jsonb_build_object()"))); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); result.parseState->unique_keys = unique_keys; result.parseState->skip_nulls = absent_on_null; for (i = 0; i < nargs; i += 2) { /* process key */ bool skip; if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); /* skip null values if absent_on_null */ skip = absent_on_null && nulls[i + 1]; /* we need to save skipped keys for the key uniqueness check */ if (skip && !unique_keys) continue; add_jsonb(args[i], false, &result, types[i], true); /* process value */ add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); } result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); return JsonbPGetDatum(JsonbValueToJsonb(result.res)); } /* * SQL function jsonb_build_object(variadic "any") */ Datum jsonb_build_object(PG_FUNCTION_ARGS) { Datum *args; bool *nulls; Oid *types; /* build argument values to build the object */ int nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); } /* * degenerate case of jsonb_build_object where it gets 0 arguments. */ Datum jsonb_build_object_noargs(PG_FUNCTION_ARGS) { JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, bool absent_on_null) { int i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) { if (absent_on_null && nulls[i]) continue; add_jsonb(args[i], nulls[i], &result, types[i], false); } result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); return JsonbPGetDatum(JsonbValueToJsonb(result.res)); } /* * SQL function jsonb_build_array(variadic "any") */ Datum jsonb_build_array(PG_FUNCTION_ARGS) { Datum *args; bool *nulls; Oid *types; /* build argument values to build the object */ int nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); } /* * degenerate case of jsonb_build_array where it gets 0 arguments. */ Datum jsonb_build_array_noargs(PG_FUNCTION_ARGS) { JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } /* * SQL function jsonb_object(text[]) * * take a one or two dimensional array of text as name value pairs * for a jsonb object. * */ Datum jsonb_object(PG_FUNCTION_ARGS) { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); Datum *in_datums; bool *in_nulls; int in_count, count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); switch (ndims) { case 0: goto close_object; break; case 1: if ((ARR_DIMS(in_array)[0]) % 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have even number of elements"))); break; case 2: if ((ARR_DIMS(in_array)[1]) != 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have two columns"))); break; default: ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); } deconstruct_array_builtin(in_array, TEXTOID, &in_datums, &in_nulls, &in_count); count = in_count / 2; for (i = 0; i < count; ++i) { JsonbValue v; char *str; int len; if (in_nulls[i * 2]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(in_datums[i * 2]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (in_nulls[i * 2 + 1]) { v.type = jbvNull; } else { str = TextDatumGetCString(in_datums[i * 2 + 1]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } pfree(in_datums); pfree(in_nulls); close_object: result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } /* * SQL function jsonb_object(text[], text[]) * * take separate name and value arrays of text to construct a jsonb object * pairwise. */ Datum jsonb_object_two_arg(PG_FUNCTION_ARGS) { ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(0); ArrayType *val_array = PG_GETARG_ARRAYTYPE_P(1); int nkdims = ARR_NDIM(key_array); int nvdims = ARR_NDIM(val_array); Datum *key_datums, *val_datums; bool *key_nulls, *val_nulls; int key_count, val_count, i; JsonbInState result; memset(&result, 0, sizeof(JsonbInState)); (void) pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); if (nkdims > 1 || nkdims != nvdims) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); if (nkdims == 0) goto close_object; deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); deconstruct_array_builtin(val_array, TEXTOID, &val_datums, &val_nulls, &val_count); if (key_count != val_count) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("mismatched array dimensions"))); for (i = 0; i < key_count; ++i) { JsonbValue v; char *str; int len; if (key_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for object key"))); str = TextDatumGetCString(key_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; (void) pushJsonbValue(&result.parseState, WJB_KEY, &v); if (val_nulls[i]) { v.type = jbvNull; } else { str = TextDatumGetCString(val_datums[i]); len = strlen(str); v.type = jbvString; v.val.string.len = len; v.val.string.val = str; } (void) pushJsonbValue(&result.parseState, WJB_VALUE, &v); } pfree(key_datums); pfree(key_nulls); pfree(val_datums); pfree(val_nulls); close_object: result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } /* * shallow clone of a parse state, suitable for use in aggregate * final functions that will only append to the values rather than * change them. */ static JsonbParseState * clone_parse_state(JsonbParseState *state) { JsonbParseState *result, *icursor, *ocursor; if (state == NULL) return NULL; result = palloc(sizeof(JsonbParseState)); icursor = state; ocursor = result; for (;;) { ocursor->contVal = icursor->contVal; ocursor->size = icursor->size; ocursor->unique_keys = icursor->unique_keys; ocursor->skip_nulls = icursor->skip_nulls; icursor = icursor->next; if (icursor == NULL) break; ocursor->next = palloc(sizeof(JsonbParseState)); ocursor = ocursor->next; } ocursor->next = NULL; return result; } static Datum jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext oldcontext, aggcontext; JsonbAggState *state; JsonbInState elem; Datum val; JsonbInState *result; bool single_scalar = false; JsonbIterator *it; Jsonb *jbelem; JsonbValue v; JsonbIteratorToken type; if (!AggCheckCallContext(fcinfo, &aggcontext)) { /* cannot be called directly because of internal-type argument */ elog(ERROR, "jsonb_agg_transfn called in non-aggregate context"); } /* set up the accumulator on the first go round */ if (PG_ARGISNULL(0)) { Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); oldcontext = MemoryContextSwitchTo(aggcontext); state = palloc(sizeof(JsonbAggState)); result = palloc0(sizeof(JsonbInState)); state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); MemoryContextSwitchTo(oldcontext); jsonb_categorize_type(arg_type, &state->val_category, &state->val_output_func); } else { state = (JsonbAggState *) PG_GETARG_POINTER(0); result = state->res; } if (absent_on_null && PG_ARGISNULL(1)) PG_RETURN_POINTER(state); /* turn the argument into jsonb in the normal function context */ val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, PG_ARGISNULL(1), &elem, state->val_category, state->val_output_func, false); jbelem = JsonbValueToJsonb(elem.res); /* switch to the aggregate context for accumulation operations */ oldcontext = MemoryContextSwitchTo(aggcontext); it = JsonbIteratorInit(&jbelem->root); while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (v.val.array.rawScalar) single_scalar = true; else result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_END_ARRAY: if (!single_scalar) result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_BEGIN_OBJECT: case WJB_END_OBJECT: result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_ELEM: case WJB_KEY: case WJB_VALUE: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else if (v.type == jbvNumeric) { /* same for numeric */ v.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_uplus, NumericGetDatum(v.val.numeric))); } result->res = pushJsonbValue(&result->parseState, type, &v); break; default: elog(ERROR, "unknown jsonb iterator token type"); } } MemoryContextSwitchTo(oldcontext); PG_RETURN_POINTER(state); } /* * jsonb_agg aggregate function */ Datum jsonb_agg_transfn(PG_FUNCTION_ARGS) { return jsonb_agg_transfn_worker(fcinfo, false); } /* * jsonb_agg_strict aggregate function */ Datum jsonb_agg_strict_transfn(PG_FUNCTION_ARGS) { return jsonb_agg_transfn_worker(fcinfo, true); } Datum jsonb_agg_finalfn(PG_FUNCTION_ARGS) { JsonbAggState *arg; JsonbInState result; Jsonb *out; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); /* returns null iff no input values */ arg = (JsonbAggState *) PG_GETARG_POINTER(0); /* * We need to do a shallow clone of the argument in case the final * function is called more than once, so we avoid changing the argument. A * shallow clone is sufficient as we aren't going to change any of the * values, just add the final array end marker. */ memset(&result, 0, sizeof(JsonbInState)); result.parseState = clone_parse_state(arg->res->parseState); result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); out = JsonbValueToJsonb(result.res); PG_RETURN_POINTER(out); } static Datum jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null, bool unique_keys) { MemoryContext oldcontext, aggcontext; JsonbInState elem; JsonbAggState *state; Datum val; JsonbInState *result; bool single_scalar; JsonbIterator *it; Jsonb *jbkey, *jbval; JsonbValue v; JsonbIteratorToken type; bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { /* cannot be called directly because of internal-type argument */ elog(ERROR, "jsonb_object_agg_transfn called in non-aggregate context"); } /* set up the accumulator on the first go round */ if (PG_ARGISNULL(0)) { Oid arg_type; oldcontext = MemoryContextSwitchTo(aggcontext); state = palloc(sizeof(JsonbAggState)); result = palloc0(sizeof(JsonbInState)); state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); result->parseState->unique_keys = unique_keys; result->parseState->skip_nulls = absent_on_null; MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); jsonb_categorize_type(arg_type, &state->key_category, &state->key_output_func); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); if (arg_type == InvalidOid) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine input data type"))); jsonb_categorize_type(arg_type, &state->val_category, &state->val_output_func); } else { state = (JsonbAggState *) PG_GETARG_POINTER(0); result = state->res; } /* turn the argument into jsonb in the normal function context */ if (PG_ARGISNULL(1)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); /* * Skip null values if absent_on_null unless key uniqueness check is * needed (because we must save keys in this case). */ skip = absent_on_null && PG_ARGISNULL(2); if (skip && !unique_keys) PG_RETURN_POINTER(state); val = PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, false, &elem, state->key_category, state->key_output_func, true); jbkey = JsonbValueToJsonb(elem.res); val = PG_ARGISNULL(2) ? (Datum) 0 : PG_GETARG_DATUM(2); memset(&elem, 0, sizeof(JsonbInState)); datum_to_jsonb(val, PG_ARGISNULL(2), &elem, state->val_category, state->val_output_func, false); jbval = JsonbValueToJsonb(elem.res); it = JsonbIteratorInit(&jbkey->root); /* switch to the aggregate context for accumulation operations */ oldcontext = MemoryContextSwitchTo(aggcontext); /* * keys should be scalar, and we should have already checked for that * above when calling datum_to_jsonb, so we only need to look for these * things. */ while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (!v.val.array.rawScalar) elog(ERROR, "unexpected structure for key"); break; case WJB_ELEM: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("object keys must be strings"))); } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); if (skip) { v.type = jbvNull; result->res = pushJsonbValue(&result->parseState, WJB_VALUE, &v); MemoryContextSwitchTo(oldcontext); PG_RETURN_POINTER(state); } break; case WJB_END_ARRAY: break; default: elog(ERROR, "unexpected structure for key"); break; } } it = JsonbIteratorInit(&jbval->root); single_scalar = false; /* * values can be anything, including structured and null, so we treat them * as in json_agg_transfn, except that single scalars are always pushed as * WJB_VALUE items. */ while ((type = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { switch (type) { case WJB_BEGIN_ARRAY: if (v.val.array.rawScalar) single_scalar = true; else result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_END_ARRAY: if (!single_scalar) result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_BEGIN_OBJECT: case WJB_END_OBJECT: result->res = pushJsonbValue(&result->parseState, type, NULL); break; case WJB_ELEM: case WJB_KEY: case WJB_VALUE: if (v.type == jbvString) { /* copy string values in the aggregate context */ char *buf = palloc(v.val.string.len + 1); snprintf(buf, v.val.string.len + 1, "%s", v.val.string.val); v.val.string.val = buf; } else if (v.type == jbvNumeric) { /* same for numeric */ v.val.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_uplus, NumericGetDatum(v.val.numeric))); } result->res = pushJsonbValue(&result->parseState, single_scalar ? WJB_VALUE : type, &v); break; default: elog(ERROR, "unknown jsonb iterator token type"); } } MemoryContextSwitchTo(oldcontext); PG_RETURN_POINTER(state); } /* * jsonb_object_agg aggregate function */ Datum jsonb_object_agg_transfn(PG_FUNCTION_ARGS) { return jsonb_object_agg_transfn_worker(fcinfo, false, false); } /* * jsonb_object_agg_strict aggregate function */ Datum jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS) { return jsonb_object_agg_transfn_worker(fcinfo, true, false); } /* * jsonb_object_agg_unique aggregate function */ Datum jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS) { return jsonb_object_agg_transfn_worker(fcinfo, false, true); } /* * jsonb_object_agg_unique_strict aggregate function */ Datum jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) { return jsonb_object_agg_transfn_worker(fcinfo, true, true); } Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { JsonbAggState *arg; JsonbInState result; Jsonb *out; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); /* returns null iff no input values */ arg = (JsonbAggState *) PG_GETARG_POINTER(0); /* * We need to do a shallow clone of the argument's res field in case the * final function is called more than once, so we avoid changing the * aggregate state value. A shallow clone is sufficient as we aren't * going to change any of the values, just add the final object end * marker. */ memset(&result, 0, sizeof(JsonbInState)); result.parseState = clone_parse_state(arg->res->parseState); result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); out = JsonbValueToJsonb(result.res); PG_RETURN_POINTER(out); } /* * Extract scalar value from raw-scalar pseudo-array jsonb. */ bool JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) { JsonbIterator *it; JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY; JsonbValue tmp; if (!JsonContainerIsArray(jbc) || !JsonContainerIsScalar(jbc)) { /* inform caller about actual type of container */ res->type = (JsonContainerIsArray(jbc)) ? jbvArray : jbvObject; return false; } /* * A root scalar is stored as an array of one element, so we get the array * and then its first (and only) member. */ it = JsonbIteratorInit(jbc); tok = JsonbIteratorNext(&it, &tmp, true); Assert(tok == WJB_BEGIN_ARRAY); Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar); tok = JsonbIteratorNext(&it, res, true); Assert(tok == WJB_ELEM); Assert(IsAJsonbScalar(res)); tok = JsonbIteratorNext(&it, &tmp, true); Assert(tok == WJB_END_ARRAY); tok = JsonbIteratorNext(&it, &tmp, true); Assert(tok == WJB_DONE); return true; } /* * Emit correct, translatable cast error message */ static void cannotCastJsonbValue(enum jbvType type, const char *sqltype) { static const struct { enum jbvType type; const char *msg; } messages[] = { {jbvNull, gettext_noop("cannot cast jsonb null to type %s")}, {jbvString, gettext_noop("cannot cast jsonb string to type %s")}, {jbvNumeric, gettext_noop("cannot cast jsonb numeric to type %s")}, {jbvBool, gettext_noop("cannot cast jsonb boolean to type %s")}, {jbvArray, gettext_noop("cannot cast jsonb array to type %s")}, {jbvObject, gettext_noop("cannot cast jsonb object to type %s")}, {jbvBinary, gettext_noop("cannot cast jsonb array or object to type %s")} }; int i; for (i = 0; i < lengthof(messages); i++) if (messages[i].type == type) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg(messages[i].msg, sqltype))); /* should be unreachable */ elog(ERROR, "unknown jsonb type: %d", (int) type); } Datum jsonb_bool(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvBool) cannotCastJsonbValue(v.type, "boolean"); PG_FREE_IF_COPY(in, 0); PG_RETURN_BOOL(v.val.boolean); } Datum jsonb_numeric(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Numeric retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "numeric"); /* * v.val.numeric points into jsonb body, so we need to make a copy to * return */ retValue = DatumGetNumericCopy(NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_NUMERIC(retValue); } Datum jsonb_int2(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Datum retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "smallint"); retValue = DirectFunctionCall1(numeric_int2, NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_DATUM(retValue); } Datum jsonb_int4(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Datum retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "integer"); retValue = DirectFunctionCall1(numeric_int4, NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_DATUM(retValue); } Datum jsonb_int8(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Datum retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "bigint"); retValue = DirectFunctionCall1(numeric_int8, NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_DATUM(retValue); } Datum jsonb_float4(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Datum retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "real"); retValue = DirectFunctionCall1(numeric_float4, NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_DATUM(retValue); } Datum jsonb_float8(PG_FUNCTION_ARGS) { Jsonb *in = PG_GETARG_JSONB_P(0); JsonbValue v; Datum retValue; if (!JsonbExtractScalar(&in->root, &v) || v.type != jbvNumeric) cannotCastJsonbValue(v.type, "double precision"); retValue = DirectFunctionCall1(numeric_float8, NumericGetDatum(v.val.numeric)); PG_FREE_IF_COPY(in, 0); PG_RETURN_DATUM(retValue); }