diff options
Diffstat (limited to 'src/backend/utils/adt/jsonb.c')
-rw-r--r-- | src/backend/utils/adt/jsonb.c | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c new file mode 100644 index 00000000000..b30e79e425b --- /dev/null +++ b/src/backend/utils/adt/jsonb.c @@ -0,0 +1,468 @@ +/*------------------------------------------------------------------------- + * + * jsonb.c + * I/O routines for jsonb type + * + * Copyright (c) 2014, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqformat.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonapi.h" +#include "utils/jsonb.h" + +typedef struct JsonbInState +{ + JsonbParseState *parseState; + JsonbValue *res; +} JsonbInState; + +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); +char *JsonbToCString(StringInfo out, char *in, int estimated_len); + +/* + * 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(0); + char *out; + + out = JsonbToCString(NULL, VARDATA(jb), 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(0); + StringInfoData buf; + StringInfo jtext = makeStringInfo(); + int version = 1; + + (void) JsonbToCString(jtext, VARDATA(jb), VARSIZE(jb)); + + pq_begintypsend(&buf); + pq_sendint(&buf, version, 1); + pq_sendtext(&buf, jtext->data, jtext->len); + pfree(jtext->data); + pfree(jtext); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * 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(0); + JsonbIterator *it; + JsonbValue v; + char *result; + + if (JB_ROOT_IS_OBJECT(in)) + result = "object"; + else if (JB_ROOT_IS_ARRAY(in) && !JB_ROOT_IS_SCALAR(in)) + result = "array"; + else + { + Assert(JB_ROOT_IS_SCALAR(in)); + + it = JsonbIteratorInit(VARDATA_ANY(in)); + + /* + * A root scalar is stored as an array of one element, so we get the + * array and then its first (and only) member. + */ + (void) JsonbIteratorNext(&it, &v, true); + Assert(v.type == jbvArray); + (void) JsonbIteratorNext(&it, &v, true); + switch (v.type) + { + case jbvNull: + result = "null"; + break; + case jbvString: + result = "string"; + break; + case jbvNumeric: + result = "number"; + break; + case jbvBool: + result = "boolean"; + break; + default: + elog(ERROR, "unknown jsonb scalar type"); + } + } + + 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, 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(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_POSMASK) + 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_POSMASK))); + + 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.string.len = checkStringLen(strlen(fname)); + v.string.val = pnstrdup(fname, v.string.len); + v.estSize = sizeof(JEntry) + v.string.len; + + _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->string.val, scalarVal->string.len)); + break; + case jbvNumeric: + appendStringInfoString(out, + DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(scalarVal->numeric)))); + break; + case jbvBool: + if (scalarVal->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; + + v.estSize = sizeof(JEntry); + + switch (tokentype) + { + + case JSON_TOKEN_STRING: + Assert (token != NULL); + v.type = jbvString; + v.string.len = checkStringLen(strlen(token)); + v.string.val = pnstrdup(token, v.string.len); + v.estSize += v.string.len; + 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; + v.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(token), 0, -1)); + v.estSize += VARSIZE_ANY(v.numeric) + sizeof(JEntry) /* alignment */ ; + break; + case JSON_TOKEN_TRUE: + v.type = jbvBool; + v.boolean = true; + break; + case JSON_TOKEN_FALSE: + v.type = jbvBool; + v.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.array.rawScalar = true; + va.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, JsonbSuperHeader in, int estimated_len) +{ + bool first = true; + JsonbIterator *it; + int type = 0; + JsonbValue v; + int level = 0; + bool redo_switch = 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, ", ", 2); + first = true; + + if (!v.array.rawScalar) + appendStringInfoChar(out, '['); + level++; + break; + case WJB_BEGIN_OBJECT: + if (!first) + appendBinaryStringInfo(out, ", ", 2); + first = true; + appendStringInfoCharMacro(out, '{'); + + level++; + break; + case WJB_KEY: + if (!first) + appendBinaryStringInfo(out, ", ", 2); + first = true; + + /* 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, ", ", 2); + else + first = false; + + jsonb_put_escaped_value(out, &v); + break; + case WJB_END_ARRAY: + level--; + if (!v.array.rawScalar) + appendStringInfoChar(out, ']'); + first = false; + break; + case WJB_END_OBJECT: + level--; + appendStringInfoCharMacro(out, '}'); + first = false; + break; + default: + elog(ERROR, "unknown flag of jsonb iterator"); + } + } + + Assert(level == 0); + + return out->data; +} |