diff options
Diffstat (limited to 'src/backend/utils/adt/jsonb.c')
-rw-r--r-- | src/backend/utils/adt/jsonb.c | 2087 |
1 files changed, 2087 insertions, 0 deletions
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c new file mode 100644 index 0000000..8d1e7fb --- /dev/null +++ b/src/backend/utils/adt/jsonb.c @@ -0,0 +1,2087 @@ +/*------------------------------------------------------------------------- + * + * jsonb.c + * I/O routines for jsonb type + * + * Copyright (c) 2014-2021, 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_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; +} 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); +static size_t checkStringLen(size_t len); +static void jsonb_in_object_start(void *pstate); +static void jsonb_in_object_end(void *pstate); +static void jsonb_in_array_start(void *pstate); +static void jsonb_in_array_end(void *pstate); +static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); +static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); +static void 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)); +} + +/* + * 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); +} + +/* + * 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 *jbv) +{ + switch (jbv->type) + { + case jbvBinary: + return JsonbContainerTypeName(jbv->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 (jbv->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", + jbv->val.datetime.typid); + } + return "unknown"; + default: + elog(ERROR, "unrecognized jsonb value type: %d", jbv->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. + */ +static inline Datum +jsonb_from_cstring(char *json, int len) +{ + JsonLexContext *lex; + JsonbInState state; + JsonSemAction sem; + + memset(&state, 0, sizeof(state)); + memset(&sem, 0, sizeof(sem)); + lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + + 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; + + pg_parse_json_or_ereport(lex, &sem); + + /* after parsing, the item member has the composed jsonb structure */ + PG_RETURN_POINTER(JsonbValueToJsonb(state.res)); +} + +static size_t +checkStringLen(size_t len) +{ + if (len > JENTRY_OFFLENMASK) + ereport(ERROR, + (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 len; +} + +static void +jsonb_in_object_start(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); +} + +static void +jsonb_in_object_end(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_END_OBJECT, NULL); +} + +static void +jsonb_in_array_start(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_ARRAY, NULL); +} + +static void +jsonb_in_array_end(void *pstate) +{ + JsonbInState *_state = (JsonbInState *) pstate; + + _state->res = pushJsonbValue(&_state->parseState, WJB_END_ARRAY, NULL); +} + +static void +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 = checkStringLen(strlen(fname)); + v.val.string.val = fname; + + _state->res = pushJsonbValue(&_state->parseState, WJB_KEY, &v); +} + +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 void +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 = checkStringLen(strlen(token)); + 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; + numd = DirectFunctionCall3(numeric_in, + CStringGetDatum(token), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + 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"); + } + } +} + +/* + * 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) + { + int i; + + appendStringInfoCharMacro(out, '\n'); + for (i = 0; i < level; i++) + appendBinaryStringInfo(out, " ", 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. + */ +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 = checkStringLen(strlen(outputstr)); + 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); +} + +/* + * 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)); +} + +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + int nargs; + int i; + JsonbInState result; + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the object */ + nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + 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); + + for (i = 0; i < nargs; i += 2) + { + /* process key */ + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument %d: key must not be null", i + 1))); + + 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); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + +/* + * 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)); +} + +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + int nargs; + int i; + JsonbInState result; + Datum *args; + bool *nulls; + Oid *types; + + /* build argument values to build the array */ + nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + memset(&result, 0, sizeof(JsonbInState)); + + result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); + + for (i = 0; i < nargs; i++) + add_jsonb(args[i], nulls[i], &result, types[i], false); + + result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); +} + +/* + * 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(in_array, + TEXTOID, -1, false, TYPALIGN_INT, + &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(key_array, + TEXTOID, -1, false, TYPALIGN_INT, + &key_datums, &key_nulls, &key_count); + + deconstruct_array(val_array, + TEXTOID, -1, false, TYPALIGN_INT, + &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; + icursor = icursor->next; + if (icursor == NULL) + break; + ocursor->next = palloc(sizeof(JsonbParseState)); + ocursor = ocursor->next; + } + ocursor->next = NULL; + + return result; +} + + +/* + * jsonb_agg aggregate function + */ +Datum +jsonb_agg_transfn(PG_FUNCTION_ARGS) +{ + 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; + } + + /* 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); +} + +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. + */ + + 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); +} + +/* + * jsonb_object_agg aggregate function + */ +Datum +jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +{ + MemoryContext oldcontext, + aggcontext; + JsonbInState elem; + JsonbAggState *state; + Datum val; + JsonbInState *result; + bool single_scalar; + JsonbIterator *it; + Jsonb *jbkey, + *jbval; + JsonbValue v; + JsonbIteratorToken type; + + 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); + 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"))); + + 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); + 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); +} + +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. + */ + + 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); +} |