aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt')
-rw-r--r--src/backend/utils/adt/Makefile10
-rw-r--r--src/backend/utils/adt/json.c42
-rw-r--r--src/backend/utils/adt/jsonb.c468
-rw-r--r--src/backend/utils/adt/jsonb_gin.c646
-rw-r--r--src/backend/utils/adt/jsonb_op.c295
-rw-r--r--src/backend/utils/adt/jsonb_util.c1872
-rw-r--r--src/backend/utils/adt/jsonfuncs.c1151
-rw-r--r--src/backend/utils/adt/numeric.c38
8 files changed, 4410 insertions, 112 deletions
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 644687954b2..6b23069e26c 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -21,11 +21,11 @@ OBJS = acl.o arrayfuncs.o array_selfuncs.o array_typanalyze.o \
cash.o char.o date.o datetime.o datum.o dbsize.o domains.o \
encode.o enum.o float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o inet_cidr_ntop.o inet_net_pton.o int.o \
- int8.o json.o jsonfuncs.o like.o \
- lockfuncs.o mac.o misc.o nabstime.o name.o network.o numeric.o \
- numutils.o oid.o oracle_compat.o orderedsetaggs.o \
- pg_lzcompress.o pg_locale.o pg_lsn.o pgstatfuncs.o \
- pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
+ int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
+ jsonfuncs.o like.o lockfuncs.o mac.o misc.o nabstime.o name.o \
+ network.o numeric.o numutils.o oid.o oracle_compat.o \
+ orderedsetaggs.o pg_lzcompress.o pg_locale.o pg_lsn.o \
+ pgstatfuncs.o pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
rangetypes_selfuncs.o rangetypes_spgist.o rangetypes_typanalyze.o \
regexp.o regproc.o ri_triggers.o rowtypes.o ruleutils.o \
selfuncs.o tid.o timestamp.o trigfuncs.o \
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 97a0e9f211e..c34a1bb50be 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -210,22 +210,17 @@ Datum
json_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
- text *result;
char *str;
int nbytes;
JsonLexContext *lex;
str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
- result = palloc(nbytes + VARHDRSZ);
- SET_VARSIZE(result, nbytes + VARHDRSZ);
- memcpy(VARDATA(result), str, nbytes);
-
/* Validate it. */
- lex = makeJsonLexContext(result, false);
+ lex = makeJsonLexContextCstringLen(str, nbytes, false);
pg_parse_json(lex, &nullSemAction);
- PG_RETURN_TEXT_P(result);
+ PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes));
}
/*
@@ -236,15 +231,26 @@ json_recv(PG_FUNCTION_ARGS)
*
* Without is better as it makes the processing faster, so only make one
* if really required.
+ *
+ * If you already have the json as a text* value, use the first of these
+ * functions, otherwise use makeJsonLexContextCstringLen().
*/
JsonLexContext *
makeJsonLexContext(text *json, bool need_escapes)
{
+ return makeJsonLexContextCstringLen(VARDATA(json),
+ VARSIZE(json) - VARHDRSZ,
+ need_escapes);
+}
+
+JsonLexContext *
+makeJsonLexContextCstringLen(char *json, int len, bool need_escapes)
+{
JsonLexContext *lex = palloc0(sizeof(JsonLexContext));
- lex->input = lex->token_terminator = lex->line_start = VARDATA(json);
+ lex->input = lex->token_terminator = lex->line_start = json;
lex->line_number = 1;
- lex->input_length = VARSIZE(json) - VARHDRSZ;
+ lex->input_length = len;
if (need_escapes)
lex->strval = makeStringInfo();
return lex;
@@ -1274,7 +1280,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
pfree(outputstr);
break;
case TYPCATEGORY_JSON:
- /* JSON will already be escaped */
+ /* JSON and JSONB will already be escaped */
outputstr = OidOutputFunctionCall(typoutputfunc, val);
appendStringInfoString(result, outputstr);
pfree(outputstr);
@@ -1406,7 +1412,7 @@ array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds)
tcategory = TYPCATEGORY_JSON_CAST;
else if (element_type == RECORDOID)
tcategory = TYPCATEGORY_COMPOSITE;
- else if (element_type == JSONOID)
+ else if (element_type == JSONOID || element_type == JSONBOID)
tcategory = TYPCATEGORY_JSON;
else
tcategory = TypeCategory(element_type);
@@ -1501,7 +1507,8 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
tcategory = TYPCATEGORY_ARRAY;
else if (tupdesc->attrs[i]->atttypid == RECORDOID)
tcategory = TYPCATEGORY_COMPOSITE;
- else if (tupdesc->attrs[i]->atttypid == JSONOID)
+ else if (tupdesc->attrs[i]->atttypid == JSONOID ||
+ tupdesc->attrs[i]->atttypid == JSONBOID)
tcategory = TYPCATEGORY_JSON;
else
tcategory = TypeCategory(tupdesc->attrs[i]->atttypid);
@@ -1689,7 +1696,7 @@ to_json(PG_FUNCTION_ARGS)
tcategory = TYPCATEGORY_ARRAY;
else if (val_type == RECORDOID)
tcategory = TYPCATEGORY_COMPOSITE;
- else if (val_type == JSONOID)
+ else if (val_type == JSONOID || val_type == JSONBOID)
tcategory = TYPCATEGORY_JSON;
else
tcategory = TypeCategory(val_type);
@@ -1783,7 +1790,7 @@ json_agg_transfn(PG_FUNCTION_ARGS)
tcategory = TYPCATEGORY_ARRAY;
else if (val_type == RECORDOID)
tcategory = TYPCATEGORY_COMPOSITE;
- else if (val_type == JSONOID)
+ else if (val_type == JSONOID || val_type == JSONBOID)
tcategory = TYPCATEGORY_JSON;
else
tcategory = TypeCategory(val_type);
@@ -2346,12 +2353,15 @@ escape_json(StringInfo buf, const char *str)
Datum
json_typeof(PG_FUNCTION_ARGS)
{
- text *json = PG_GETARG_TEXT_P(0);
+ text *json;
- JsonLexContext *lex = makeJsonLexContext(json, false);
+ JsonLexContext *lex;
JsonTokenType tok;
char *type;
+ json = PG_GETARG_TEXT_P(0);
+ lex = makeJsonLexContext(json, false);
+
/* Lex exactly one token from the input and check its type. */
json_lex(lex);
tok = lex_peek(lex);
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;
+}
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
new file mode 100644
index 00000000000..4a6b8fd6888
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_gin.c
@@ -0,0 +1,646 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_gin.c
+ * GIN support functions for jsonb
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonb_gin.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/gin.h"
+#include "access/skey.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+
+typedef struct PathHashStack
+{
+ uint32 hash;
+ struct PathHashStack *parent;
+} PathHashStack;
+
+static text *make_text_key(const char *str, int len, char flag);
+static text *make_scalar_key(const JsonbValue * scalarVal, char flag);
+
+/*
+ *
+ * jsonb_ops GIN opclass support functions
+ *
+ */
+Datum
+gin_compare_jsonb(PG_FUNCTION_ARGS)
+{
+ text *arg1 = PG_GETARG_TEXT_PP(0);
+ text *arg2 = PG_GETARG_TEXT_PP(1);
+ int32 result;
+ char *a1p,
+ *a2p;
+ int len1,
+ len2;
+
+ a1p = VARDATA_ANY(arg1);
+ a2p = VARDATA_ANY(arg2);
+
+ len1 = VARSIZE_ANY_EXHDR(arg1);
+ len2 = VARSIZE_ANY_EXHDR(arg2);
+
+ /* Compare text as bttextcmp does, but always using C collation */
+ result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID);
+
+ PG_FREE_IF_COPY(arg1, 0);
+ PG_FREE_IF_COPY(arg2, 1);
+
+ PG_RETURN_INT32(result);
+}
+
+Datum
+gin_extract_jsonb(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = (Jsonb *) PG_GETARG_JSONB(0);
+ int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
+ Datum *entries = NULL;
+ int total = 2 * JB_ROOT_COUNT(jb);
+ int i = 0,
+ r;
+ JsonbIterator *it;
+ JsonbValue v;
+
+ if (total == 0)
+ {
+ *nentries = 0;
+ PG_RETURN_POINTER(NULL);
+ }
+
+ entries = (Datum *) palloc(sizeof(Datum) * total);
+
+ it = JsonbIteratorInit(VARDATA(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ if (i >= total)
+ {
+ total *= 2;
+ entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
+ }
+
+ /*
+ * Serialize keys and elements equivalently, but only when elements
+ * are Jsonb strings. Otherwise, serialize elements as values. Array
+ * elements are indexed as keys, for the benefit of
+ * JsonbExistsStrategyNumber. Our definition of existence does not
+ * allow for checking the existence of a non-jbvString element (just
+ * like the definition of the underlying operator), because the
+ * operator takes a text rhs argument (which is taken as a proxy for an
+ * equivalent Jsonb string).
+ *
+ * The way existence is represented does not preclude an alternative
+ * existence operator, that takes as its rhs value an arbitrarily
+ * internally-typed Jsonb. The only reason that isn't the case here is
+ * that the existence operator is only really intended to determine if
+ * an object has a certain key (object pair keys are of course
+ * invariably strings), which is extended to jsonb arrays. You could
+ * think of the default Jsonb definition of existence as being
+ * equivalent to a definition where all types of scalar array elements
+ * are keys that we can check the existence of, while just forbidding
+ * non-string notation. This inflexibility prevents the user from
+ * having to qualify that the rhs string is a raw scalar string (that
+ * is, naturally no internal string quoting in required for the text
+ * argument), and allows us to not set the reset flag for
+ * JsonbExistsStrategyNumber, since we know that keys are strings for
+ * both objects and arrays, and don't have to further account for type
+ * mismatch. Not having to set the reset flag makes it less than
+ * tempting to tighten up the definition of existence to preclude array
+ * elements entirely, which would arguably be a simpler alternative.
+ * In any case the infrastructure used to implement the existence
+ * operator could trivially support this hypothetical, slightly
+ * distinct definition of existence.
+ */
+ switch (r)
+ {
+ case WJB_KEY:
+ /* Serialize key separately, for existence strategies */
+ entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM));
+ break;
+ case WJB_ELEM:
+ if (v.type == jbvString)
+ entries[i++] = PointerGetDatum(make_scalar_key(&v, JKEYELEM));
+ else
+ entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL));
+ break;
+ case WJB_VALUE:
+ entries[i++] = PointerGetDatum(make_scalar_key(&v, JVAL));
+ break;
+ default:
+ continue;
+ }
+ }
+
+ *nentries = i;
+
+ PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_extract_jsonb_query(PG_FUNCTION_ARGS)
+{
+ int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
+ StrategyNumber strategy = PG_GETARG_UINT16(2);
+ int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
+ Datum *entries;
+
+ if (strategy == JsonbContainsStrategyNumber)
+ {
+ /* Query is a jsonb, so just apply gin_extract_jsonb... */
+ entries = (Datum *)
+ DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
+ PG_GETARG_DATUM(0),
+ PointerGetDatum(nentries)));
+ /* ...although "contains {}" requires a full index scan */
+ if (entries == NULL)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else if (strategy == JsonbExistsStrategyNumber)
+ {
+ text *query = PG_GETARG_TEXT_PP(0);
+ text *item;
+
+ *nentries = 1;
+ entries = (Datum *) palloc(sizeof(Datum));
+ item = make_text_key(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query),
+ JKEYELEM);
+ entries[0] = PointerGetDatum(item);
+ }
+ else if (strategy == JsonbExistsAnyStrategyNumber ||
+ strategy == JsonbExistsAllStrategyNumber)
+ {
+ ArrayType *query = PG_GETARG_ARRAYTYPE_P(0);
+ Datum *key_datums;
+ bool *key_nulls;
+ int key_count;
+ int i,
+ j;
+ text *item;
+
+ deconstruct_array(query,
+ TEXTOID, -1, false, 'i',
+ &key_datums, &key_nulls, &key_count);
+
+ entries = (Datum *) palloc(sizeof(Datum) * key_count);
+
+ for (i = 0, j = 0; i < key_count; ++i)
+ {
+ /* Nulls in the array are ignored */
+ if (key_nulls[i])
+ continue;
+ item = make_text_key(VARDATA(key_datums[i]),
+ VARSIZE(key_datums[i]) - VARHDRSZ,
+ JKEYELEM);
+ entries[j++] = PointerGetDatum(item);
+ }
+
+ *nentries = j;
+ /* ExistsAll with no keys should match everything */
+ if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+ entries = NULL; /* keep compiler quiet */
+ }
+
+ PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_consistent_jsonb(PG_FUNCTION_ARGS)
+{
+ bool *check = (bool *) PG_GETARG_POINTER(0);
+ StrategyNumber strategy = PG_GETARG_UINT16(1);
+
+ /* Jsonb *query = PG_GETARG_JSONB(2); */
+ int32 nkeys = PG_GETARG_INT32(3);
+
+ /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ bool *recheck = (bool *) PG_GETARG_POINTER(5);
+ bool res = true;
+ int32 i;
+
+ if (strategy == JsonbContainsStrategyNumber)
+ {
+ /*
+ * Index doesn't have information about correspondence of Jsonb keys
+ * and values (as distinct from GIN keys, which a key/value pair is
+ * stored as), so invariably we recheck. Besides, there are some
+ * special rules around the containment of raw scalar arrays and
+ * regular arrays that are not represented here. However, if all of
+ * the keys are not present, that's sufficient reason to return false
+ * and finish immediately.
+ */
+ *recheck = true;
+ for (i = 0; i < nkeys; i++)
+ {
+ if (!check[i])
+ {
+ res = false;
+ break;
+ }
+ }
+ }
+ else if (strategy == JsonbExistsStrategyNumber)
+ {
+ /* Existence of key guaranteed in default search mode */
+ *recheck = false;
+ res = true;
+ }
+ else if (strategy == JsonbExistsAnyStrategyNumber)
+ {
+ /* Existence of key guaranteed in default search mode */
+ *recheck = false;
+ res = true;
+ }
+ else if (strategy == JsonbExistsAllStrategyNumber)
+ {
+ /* Testing for the presence of all keys gives an exact result */
+ *recheck = false;
+ for (i = 0; i < nkeys; i++)
+ {
+ if (!check[i])
+ {
+ res = false;
+ break;
+ }
+ }
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
+{
+ GinLogicValue *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+ StrategyNumber strategy = PG_GETARG_UINT16(1);
+ /* Jsonb *query = PG_GETARG_JSONB(2); */
+ int32 nkeys = PG_GETARG_INT32(3);
+ /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ GinLogicValue res = GIN_TRUE;
+
+ int32 i;
+
+ if (strategy == JsonbContainsStrategyNumber)
+ {
+ bool has_maybe = false;
+
+ /*
+ * All extracted keys must be present. Combination of GIN_MAYBE and
+ * GIN_TRUE gives GIN_MAYBE result because then all keys may be
+ * present.
+ */
+ for (i = 0; i < nkeys; i++)
+ {
+ if (check[i] == GIN_FALSE)
+ {
+ res = GIN_FALSE;
+ break;
+ }
+ if (check[i] == GIN_MAYBE)
+ {
+ res = GIN_MAYBE;
+ has_maybe = true;
+ }
+ }
+
+ /*
+ * Index doesn't have information about correspondence of Jsonb keys
+ * and values (as distinct from GIN keys, which a key/value pair is
+ * stored as), so invariably we recheck. This is also reflected in how
+ * GIN_MAYBE is given in response to there being no GIN_MAYBE input.
+ */
+ if (!has_maybe && res == GIN_TRUE)
+ res = GIN_MAYBE;
+ }
+ else if (strategy == JsonbExistsStrategyNumber ||
+ strategy == JsonbExistsAnyStrategyNumber)
+ {
+ /* Existence of key guaranteed in default search mode */
+ res = GIN_FALSE;
+ for (i = 0; i < nkeys; i++)
+ {
+ if (check[i] == GIN_TRUE)
+ {
+ res = GIN_TRUE;
+ break;
+ }
+ if (check[i] == GIN_MAYBE)
+ {
+ res = GIN_MAYBE;
+ }
+ }
+ }
+ else if (strategy == JsonbExistsAllStrategyNumber)
+ {
+ /* Testing for the presence of all keys gives an exact result */
+ for (i = 0; i < nkeys; i++)
+ {
+ if (check[i] == GIN_FALSE)
+ {
+ res = GIN_FALSE;
+ break;
+ }
+ if (check[i] == GIN_MAYBE)
+ {
+ res = GIN_MAYBE;
+ }
+ }
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+ PG_RETURN_GIN_LOGIC_VALUE(res);
+}
+
+/*
+ *
+ * jsonb_hash_ops GIN opclass support functions
+ *
+ */
+Datum
+gin_consistent_jsonb_hash(PG_FUNCTION_ARGS)
+{
+ bool *check = (bool *) PG_GETARG_POINTER(0);
+ StrategyNumber strategy = PG_GETARG_UINT16(1);
+ /* Jsonb *query = PG_GETARG_JSONB(2); */
+ int32 nkeys = PG_GETARG_INT32(3);
+ /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ bool *recheck = (bool *) PG_GETARG_POINTER(5);
+ bool res = true;
+ int32 i;
+
+ if (strategy != JsonbContainsStrategyNumber)
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+ /*
+ * jsonb_hash_ops index doesn't have information about correspondence
+ * of Jsonb keys and values (as distinct from GIN keys, which a
+ * key/value pair is stored as), so invariably we recheck. Besides,
+ * there are some special rules around the containment of raw scalar
+ * arrays and regular arrays that are not represented here. However,
+ * if all of the keys are not present, that's sufficient reason to
+ * return false and finish immediately.
+ */
+ *recheck = true;
+ for (i = 0; i < nkeys; i++)
+ {
+ if (!check[i])
+ {
+ res = false;
+ break;
+ }
+ }
+
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+gin_triconsistent_jsonb_hash(PG_FUNCTION_ARGS)
+{
+ GinLogicValue *check = (GinLogicValue *) PG_GETARG_POINTER(0);
+ StrategyNumber strategy = PG_GETARG_UINT16(1);
+ /* Jsonb *query = PG_GETARG_JSONB(2); */
+ int32 nkeys = PG_GETARG_INT32(3);
+ /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ GinLogicValue res = GIN_TRUE;
+ int32 i;
+ bool has_maybe = false;
+
+ if (strategy != JsonbContainsStrategyNumber)
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+ /*
+ * All extracted keys must be present. A combination of GIN_MAYBE and
+ * GIN_TRUE induces a GIN_MAYBE result, because then all keys may be
+ * present.
+ */
+ for (i = 0; i < nkeys; i++)
+ {
+ if (check[i] == GIN_FALSE)
+ {
+ res = GIN_FALSE;
+ break;
+ }
+ if (check[i] == GIN_MAYBE)
+ {
+ res = GIN_MAYBE;
+ has_maybe = true;
+ }
+ }
+
+ /*
+ * jsonb_hash_ops index doesn't have information about correspondence of
+ * Jsonb keys and values (as distinct from GIN keys, which for this opclass
+ * are a hash of a pair, or a hash of just an element), so invariably we
+ * recheck. This is also reflected in how GIN_MAYBE is given in response
+ * to there being no GIN_MAYBE input.
+ */
+ if (!has_maybe && res == GIN_TRUE)
+ res = GIN_MAYBE;
+
+ PG_RETURN_GIN_LOGIC_VALUE(res);
+}
+
+Datum
+gin_extract_jsonb_hash(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
+ int total = 2 * JB_ROOT_COUNT(jb);
+ JsonbIterator *it;
+ JsonbValue v;
+ PathHashStack tail;
+ PathHashStack *stack;
+ int i = 0,
+ r;
+ Datum *entries = NULL;
+
+ if (total == 0)
+ {
+ *nentries = 0;
+ PG_RETURN_POINTER(NULL);
+ }
+
+ entries = (Datum *) palloc(sizeof(Datum) * total);
+
+ it = JsonbIteratorInit(VARDATA(jb));
+
+ tail.parent = NULL;
+ tail.hash = 0;
+ stack = &tail;
+
+ while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ PathHashStack *tmp;
+
+ if (i >= total)
+ {
+ total *= 2;
+ entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
+ }
+
+ switch (r)
+ {
+ case WJB_BEGIN_ARRAY:
+ case WJB_BEGIN_OBJECT:
+ tmp = stack;
+ stack = (PathHashStack *) palloc(sizeof(PathHashStack));
+
+ /*
+ * Nesting an array within another array will not alter
+ * innermost scalar element hash values, but that seems
+ * inconsequential
+ */
+ if (tmp->parent)
+ {
+ /*
+ * We pass forward hashes from previous container nesting
+ * levels so that nested arrays with an outermost nested
+ * object will have element hashes mixed with the outermost
+ * key. It's also somewhat useful to have nested objects
+ * innermost values have hashes that are a function of not
+ * just their own key, but outer keys too.
+ */
+ stack->hash = tmp->hash;
+ }
+ else
+ {
+ /*
+ * At least nested level, initialize with stable container
+ * type proxy value
+ */
+ stack->hash = (r == WJB_BEGIN_ARRAY)? JB_FARRAY:JB_FOBJECT;
+ }
+ stack->parent = tmp;
+ break;
+ case WJB_KEY:
+ /* Initialize hash from parent */
+ stack->hash = stack->parent->hash;
+ JsonbHashScalarValue(&v, &stack->hash);
+ break;
+ case WJB_ELEM:
+ /* Elements have parent hash mixed in separately */
+ stack->hash = stack->parent->hash;
+ case WJB_VALUE:
+ /* Element/value case */
+ JsonbHashScalarValue(&v, &stack->hash);
+ entries[i++] = stack->hash;
+ break;
+ case WJB_END_ARRAY:
+ case WJB_END_OBJECT:
+ /* Pop the stack */
+ tmp = stack->parent;
+ pfree(stack);
+ stack = tmp;
+ break;
+ default:
+ elog(ERROR, "invalid JsonbIteratorNext rc: %d", r);
+ }
+ }
+
+ *nentries = i;
+
+ PG_RETURN_POINTER(entries);
+}
+
+Datum
+gin_extract_jsonb_query_hash(PG_FUNCTION_ARGS)
+{
+ int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
+ StrategyNumber strategy = PG_GETARG_UINT16(2);
+ int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
+ Datum *entries;
+
+ if (strategy != JsonbContainsStrategyNumber)
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+
+ /* Query is a jsonb, so just apply gin_extract_jsonb... */
+ entries = (Datum *)
+ DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_hash,
+ PG_GETARG_DATUM(0),
+ PointerGetDatum(nentries)));
+
+ /* ...although "contains {}" requires a full index scan */
+ if (entries == NULL)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+
+ PG_RETURN_POINTER(entries);
+}
+
+/*
+ * Build a text value from a cstring and flag suitable for storage as a key
+ * value
+ */
+static text *
+make_text_key(const char *str, int len, char flag)
+{
+ text *item;
+
+ item = (text *) palloc(VARHDRSZ + len + 1);
+ SET_VARSIZE(item, VARHDRSZ + len + 1);
+
+ *VARDATA(item) = flag;
+
+ memcpy(VARDATA(item) + 1, str, len);
+
+ return item;
+}
+
+/*
+ * Create a textual representation of a jsonbValue for GIN storage.
+ */
+static text *
+make_scalar_key(const JsonbValue * scalarVal, char flag)
+{
+ text *item;
+ char *cstr;
+
+ switch (scalarVal->type)
+ {
+ case jbvNull:
+ item = make_text_key("n", 1, flag);
+ break;
+ case jbvBool:
+ item = make_text_key(scalarVal->boolean ? "t" : "f", 1, flag);
+ break;
+ case jbvNumeric:
+ /*
+ * A normalized textual representation, free of trailing zeroes is
+ * is required.
+ *
+ * It isn't ideal that numerics are stored in a relatively bulky
+ * textual format. However, it's a notationally convenient way of
+ * storing a "union" type in the GIN B-Tree, and indexing Jsonb
+ * strings takes precedence.
+ */
+ cstr = numeric_normalize(scalarVal->numeric);
+ item = make_text_key(cstr, strlen(cstr), flag);
+ pfree(cstr);
+ break;
+ case jbvString:
+ item = make_text_key(scalarVal->string.val, scalarVal->string.len,
+ flag);
+ break;
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ }
+
+ return item;
+}
diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c
new file mode 100644
index 00000000000..d6b1855c195
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_op.c
@@ -0,0 +1,295 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_op.c
+ * Special operators for jsonb only, used by various index access methods
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonb_op.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "utils/jsonb.h"
+
+Datum
+jsonb_exists(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ text *key = PG_GETARG_TEXT_PP(1);
+ JsonbValue kval;
+ JsonbValue *v = NULL;
+
+ /*
+ * We only match Object keys (which are naturally always Strings), or
+ * string elements in arrays. In particular, we do not match non-string
+ * scalar elements. Existence of a key/element is only considered at the
+ * top level. No recursion occurs.
+ */
+ kval.type = jbvString;
+ kval.string.val = VARDATA_ANY(key);
+ kval.string.len = VARSIZE_ANY_EXHDR(key);
+
+ v = findJsonbValueFromSuperHeader(VARDATA(jb),
+ JB_FOBJECT | JB_FARRAY,
+ NULL,
+ &kval);
+
+ PG_RETURN_BOOL(v != NULL);
+}
+
+Datum
+jsonb_exists_any(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1);
+ JsonbValue *arrKey = arrayToJsonbSortedArray(keys);
+ uint32 *plowbound = NULL,
+ lowbound = 0;
+ int i;
+
+ if (arrKey == NULL || arrKey->object.nPairs == 0)
+ PG_RETURN_BOOL(false);
+
+ if (JB_ROOT_IS_OBJECT(jb))
+ plowbound = &lowbound;
+
+ /*
+ * We exploit the fact that the pairs list is already sorted into strictly
+ * increasing order to narrow the findJsonbValueFromSuperHeader search;
+ * each search can start one entry past the previous "found" entry, or at
+ * the lower bound of the last search.
+ */
+ for (i = 0; i < arrKey->array.nElems; i++)
+ {
+ if (findJsonbValueFromSuperHeader(VARDATA(jb),
+ JB_FOBJECT | JB_FARRAY,
+ plowbound,
+ arrKey->array.elems + i) != NULL)
+ PG_RETURN_BOOL(true);
+ }
+
+ PG_RETURN_BOOL(false);
+}
+
+Datum
+jsonb_exists_all(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1);
+ JsonbValue *arrKey = arrayToJsonbSortedArray(keys);
+ uint32 *plowbound = NULL;
+ uint32 lowbound = 0;
+ int i;
+
+ if (arrKey == NULL || arrKey->array.nElems == 0)
+ PG_RETURN_BOOL(true);
+
+ if (JB_ROOT_IS_OBJECT(jb))
+ plowbound = &lowbound;
+
+ /*
+ * We exploit the fact that the pairs list is already sorted into strictly
+ * increasing order to narrow the findJsonbValueFromSuperHeader search;
+ * each search can start one entry past the previous "found" entry, or at
+ * the lower bound of the last search.
+ */
+ for (i = 0; i < arrKey->array.nElems; i++)
+ {
+ if (findJsonbValueFromSuperHeader(VARDATA(jb),
+ JB_FOBJECT | JB_FARRAY,
+ plowbound,
+ arrKey->array.elems + i) == NULL)
+ PG_RETURN_BOOL(false);
+ }
+
+ PG_RETURN_BOOL(true);
+}
+
+Datum
+jsonb_contains(PG_FUNCTION_ARGS)
+{
+ Jsonb *val = PG_GETARG_JSONB(0);
+ Jsonb *tmpl = PG_GETARG_JSONB(1);
+
+ JsonbIterator *it1, *it2;
+
+ if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
+ JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
+ PG_RETURN_BOOL(false);
+
+ it1 = JsonbIteratorInit(VARDATA(val));
+ it2 = JsonbIteratorInit(VARDATA(tmpl));
+
+ PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2));
+}
+
+Datum
+jsonb_contained(PG_FUNCTION_ARGS)
+{
+ /* Commutator of "contains" */
+ Jsonb *tmpl = PG_GETARG_JSONB(0);
+ Jsonb *val = PG_GETARG_JSONB(1);
+
+ JsonbIterator *it1, *it2;
+
+ if (JB_ROOT_COUNT(val) < JB_ROOT_COUNT(tmpl) ||
+ JB_ROOT_IS_OBJECT(val) != JB_ROOT_IS_OBJECT(tmpl))
+ PG_RETURN_BOOL(false);
+
+ it1 = JsonbIteratorInit(VARDATA(val));
+ it2 = JsonbIteratorInit(VARDATA(tmpl));
+
+ PG_RETURN_BOOL(JsonbDeepContains(&it1, &it2));
+}
+
+Datum
+jsonb_ne(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) != 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+/*
+ * B-Tree operator class operators, support function
+ */
+Datum
+jsonb_lt(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) < 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_gt(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) > 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_le(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) <= 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_ge(PG_FUNCTION_ARGS)
+{
+
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) >= 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_eq(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ bool res;
+
+ res = (compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb)) == 0);
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_BOOL(res);
+}
+
+Datum
+jsonb_cmp(PG_FUNCTION_ARGS)
+{
+ Jsonb *jba = PG_GETARG_JSONB(0);
+ Jsonb *jbb = PG_GETARG_JSONB(1);
+ int res;
+
+ res = compareJsonbSuperHeaderValue(VARDATA(jba), VARDATA(jbb));
+
+ PG_FREE_IF_COPY(jba, 0);
+ PG_FREE_IF_COPY(jbb, 1);
+ PG_RETURN_INT32(res);
+}
+
+/*
+ * Hash operator class jsonb hashing function
+ */
+Datum
+jsonb_hash(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ JsonbIterator *it;
+ int32 r;
+ JsonbValue v;
+ uint32 hash = 0;
+
+ if (JB_ROOT_COUNT(jb) == 0)
+ PG_RETURN_INT32(0);
+
+ it = JsonbIteratorInit(VARDATA(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ switch (r)
+ {
+ /* Rotation is left to JsonbHashScalarValue() */
+ case WJB_BEGIN_ARRAY:
+ hash ^= JB_FARRAY;
+ break;
+ case WJB_BEGIN_OBJECT:
+ hash ^= JB_FOBJECT;
+ break;
+ case WJB_KEY:
+ case WJB_VALUE:
+ case WJB_ELEM:
+ JsonbHashScalarValue(&v, &hash);
+ break;
+ case WJB_END_ARRAY:
+ case WJB_END_OBJECT:
+ break;
+ default:
+ elog(ERROR, "invalid JsonbIteratorNext rc: %d", r);
+ }
+ }
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_RETURN_INT32(hash);
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
new file mode 100644
index 00000000000..4a1d4451301
--- /dev/null
+++ b/src/backend/utils/adt/jsonb_util.c
@@ -0,0 +1,1872 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonb_util.c
+ * Utilities for jsonb datatype
+ *
+ * Copyright (c) 2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonb_util.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/hash.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/jsonb.h"
+#include "utils/memutils.h"
+
+/*
+ * Twice as many values may be stored within pairs (for an Object) than within
+ * elements (for an Array), modulo the current MaxAllocSize limitation. Note
+ * that JSONB_MAX_PAIRS is derived from the number of possible pairs, not
+ * values (as is the case for arrays and their elements), because we're
+ * concerned about limitations on the representation of the number of pairs.
+ * Over twice the memory is required to store n JsonbPairs as n JsonbValues.
+ * It only takes exactly twice as much disk space for storage, though. The
+ * JsonbPair (not an actual pair of values) representation is used here because
+ * that is what is subject to the MaxAllocSize restriction when building an
+ * object.
+ */
+#define JSONB_MAX_ELEMS (Min(MaxAllocSize / sizeof(JsonbValue), JENTRY_POSMASK))
+#define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), \
+ JENTRY_POSMASK))
+
+/*
+ * State used while converting an arbitrary JsonbValue into a Jsonb value
+ * (4-byte varlena uncompressed representation of a Jsonb)
+ *
+ * ConvertLevel: Bookkeeping around particular level when converting.
+ */
+typedef struct convertLevel
+{
+ uint32 i; /* Iterates once per element, or once per pair */
+ uint32 *header; /* Pointer to current container header */
+ JEntry *meta; /* This level's metadata */
+ char *begin; /* Pointer into convertState.buffer */
+} convertLevel;
+
+/*
+ * convertState: Overall bookkeeping state for conversion
+ */
+typedef struct convertState
+{
+ /* Preallocated buffer in which to form varlena/Jsonb value */
+ Jsonb *buffer;
+ /* Pointer into buffer */
+ char *ptr;
+
+ /* State for */
+ convertLevel *allState, /* Overall state array */
+ *contPtr; /* Cur container pointer (in allState) */
+
+ /* Current size of buffer containing allState array */
+ Size levelSz;
+
+} convertState;
+
+static int compareJsonbScalarValue(JsonbValue * a, JsonbValue * b);
+static int lexicalCompareJsonbStringValue(const void *a, const void *b);
+static Size convertJsonb(JsonbValue * val, Jsonb* buffer);
+static inline short addPaddingInt(convertState * cstate);
+static void walkJsonbValueConversion(JsonbValue * val, convertState * cstate,
+ uint32 nestlevel);
+static void putJsonbValueConversion(convertState * cstate, JsonbValue * val,
+ uint32 flags, uint32 level);
+static void putScalarConversion(convertState * cstate, JsonbValue * scalarVal,
+ uint32 level, uint32 i);
+static void iteratorFromContainerBuf(JsonbIterator * it, char *buffer);
+static bool formIterIsContainer(JsonbIterator ** it, JsonbValue * val,
+ JEntry * ent, bool skipNested);
+static JsonbIterator *freeAndGetParent(JsonbIterator * it);
+static JsonbParseState *pushState(JsonbParseState ** pstate);
+static void appendKey(JsonbParseState * pstate, JsonbValue * scalarVal);
+static void appendValue(JsonbParseState * pstate, JsonbValue * scalarVal);
+static void appendElement(JsonbParseState * pstate, JsonbValue * scalarVal);
+static int lengthCompareJsonbStringValue(const void *a, const void *b, void *arg);
+static int lengthCompareJsonbPair(const void *a, const void *b, void *arg);
+static void uniqueifyJsonbObject(JsonbValue * object);
+static void uniqueifyJsonbArray(JsonbValue * array);
+
+/*
+ * Turn an in-memory JsonbValue into a Jsonb for on-disk storage.
+ *
+ * There isn't a JsonbToJsonbValue(), because generally we find it more
+ * convenient to directly iterate through the Jsonb representation and only
+ * really convert nested scalar values. formIterIsContainer() does this, so
+ * that clients of the iteration code don't have to directly deal with the
+ * binary representation (JsonbDeepContains() is a notable exception, although
+ * all exceptions are internal to this module). In general, functions that
+ * accept a JsonbValue argument are concerned with the manipulation of scalar
+ * values, or simple containers of scalar values, where it would be
+ * inconvenient to deal with a great amount of other state.
+ */
+Jsonb *
+JsonbValueToJsonb(JsonbValue * val)
+{
+ Jsonb *out;
+ Size sz;
+
+ if (IsAJsonbScalar(val))
+ {
+ /* Scalar value */
+ JsonbParseState *pstate = NULL;
+ JsonbValue *res;
+ JsonbValue scalarArray;
+
+ scalarArray.type = jbvArray;
+ scalarArray.array.rawScalar = true;
+ scalarArray.array.nElems = 1;
+
+ pushJsonbValue(&pstate, WJB_BEGIN_ARRAY, &scalarArray);
+ pushJsonbValue(&pstate, WJB_ELEM, val);
+ res = pushJsonbValue(&pstate, WJB_END_ARRAY, NULL);
+
+ out = palloc(VARHDRSZ + res->estSize);
+ sz = convertJsonb(res, out);
+ Assert(sz <= res->estSize);
+ SET_VARSIZE(out, sz + VARHDRSZ);
+ }
+ else if (val->type == jbvObject || val->type == jbvArray)
+ {
+ out = palloc(VARHDRSZ + val->estSize);
+ sz = convertJsonb(val, out);
+ Assert(sz <= val->estSize);
+ SET_VARSIZE(out, VARHDRSZ + sz);
+ }
+ else
+ {
+ Assert(val->type == jbvBinary);
+ out = palloc(VARHDRSZ + val->binary.len);
+ SET_VARSIZE(out, VARHDRSZ + val->binary.len);
+ memcpy(VARDATA(out), val->binary.data, val->binary.len);
+ }
+
+ return out;
+}
+
+/*
+ * BT comparator worker function. Returns an integer less than, equal to, or
+ * greater than zero, indicating whether a is less than, equal to, or greater
+ * than b. Consistent with the requirements for a B-Tree operator class
+ *
+ * Strings are compared lexically, in contrast with other places where we use a
+ * much simpler comparator logic for searching through Strings. Since this is
+ * called from B-Tree support function 1, we're careful about not leaking
+ * memory here.
+ */
+int
+compareJsonbSuperHeaderValue(JsonbSuperHeader a, JsonbSuperHeader b)
+{
+ JsonbIterator *ita,
+ *itb;
+ int res = 0;
+
+ ita = JsonbIteratorInit(a);
+ itb = JsonbIteratorInit(b);
+
+ do
+ {
+ JsonbValue va,
+ vb;
+ int ra,
+ rb;
+
+ ra = JsonbIteratorNext(&ita, &va, false);
+ rb = JsonbIteratorNext(&itb, &vb, false);
+
+ /*
+ * To a limited extent we'll redundantly iterate over an array/object
+ * while re-performing the same test without any reasonable expectation
+ * of the same container types having differing lengths (as when we
+ * process a WJB_BEGIN_OBJECT, and later the corresponding
+ * WJB_END_OBJECT), but no matter.
+ */
+ if (ra == rb)
+ {
+ if (ra == WJB_DONE)
+ {
+ /* Decisively equal */
+ break;
+ }
+
+ if (va.type == vb.type)
+ {
+ switch (va.type)
+ {
+ case jbvString:
+ res = lexicalCompareJsonbStringValue(&va, &vb);
+ break;
+ case jbvNull:
+ case jbvNumeric:
+ case jbvBool:
+ res = compareJsonbScalarValue(&va, &vb);
+ break;
+ case jbvArray:
+ /*
+ * This could be a "raw scalar" pseudo array. That's a
+ * special case here though, since we still want the
+ * general type-based comparisons to apply, and as far
+ * as we're concerned a pseudo array is just a scalar.
+ */
+ if (va.array.rawScalar != vb.array.rawScalar)
+ res = (va.array.rawScalar) ? -1 : 1;
+ if (va.array.nElems != vb.array.nElems)
+ res = (va.array.nElems > vb.array.nElems) ? 1 : -1;
+ break;
+ case jbvObject:
+ if (va.object.nPairs != vb.object.nPairs)
+ res = (va.object.nPairs > vb.object.nPairs) ? 1 : -1;
+ break;
+ case jbvBinary:
+ elog(ERROR, "unexpected jbvBinary value");
+ }
+ }
+ else
+ {
+ /* Type-defined order */
+ res = (va.type > vb.type) ? 1 : -1;
+ }
+ }
+ else
+ {
+ /*
+ * It's safe to assume that the types differed.
+ *
+ * If the two values were the same container type, then there'd
+ * have been a chance to observe the variation in the number of
+ * elements/pairs (when processing WJB_BEGIN_OBJECT, say). They
+ * can't be scalar types either, because then they'd have to be
+ * contained in containers already ruled unequal due to differing
+ * numbers of pairs/elements, or already directly ruled unequal
+ * with a call to the underlying type's comparator.
+ */
+ Assert(va.type != vb.type);
+ Assert(va.type == jbvArray || va.type == jbvObject);
+ Assert(vb.type == jbvArray || vb.type == jbvObject);
+ /* Type-defined order */
+ res = (va.type > vb.type) ? 1 : -1;
+ }
+ }
+ while (res == 0);
+
+ while (ita != NULL)
+ {
+ JsonbIterator *i = ita->parent;
+ pfree(ita);
+ ita = i;
+ }
+ while (itb != NULL)
+ {
+ JsonbIterator *i = itb->parent;
+ pfree(itb);
+ itb = i;
+ }
+
+ return res;
+}
+
+/*
+ * Find value in object (i.e. the "value" part of some key/value pair in an
+ * object), or find a matching element if we're looking through an array. Do
+ * so on the basis of equality of the object keys only, or alternatively
+ * element values only, with a caller-supplied value "key". The "flags"
+ * argument allows the caller to specify which container types are of interest.
+ *
+ * This exported utility function exists to facilitate various cases concerned
+ * with "containment". If asked to look through an object, the caller had
+ * better pass a Jsonb String, because their keys can only be strings.
+ * Otherwise, for an array, any type of JsonbValue will do.
+ *
+ * In order to proceed with the search, it is necessary for callers to have
+ * both specified an interest in exactly one particular container type with an
+ * appropriate flag, as well as having the pointed-to Jsonb superheader be of
+ * one of those same container types at the top level. (Actually, we just do
+ * whichever makes sense to save callers the trouble of figuring it out - at
+ * most one can make sense, because the super header either points to an array
+ * (possible a "raw scalar" pseudo array) or an object.)
+ *
+ * Note that we can return a jbvBinary JsonbValue if this is called on an
+ * object, but we never do so on an array. If the caller asks to look through
+ * a container type that is not of the type pointed to by the superheader,
+ * immediately fall through and return NULL. If we cannot find the value,
+ * return NULL. Otherwise, return palloc()'d copy of value.
+ *
+ * lowbound can be NULL, but if not it's used to establish a point at which to
+ * start searching. If the value searched for is found, then lowbound is then
+ * set to an offset into the array or object. Typically, this is used to
+ * exploit the ordering of objects to avoid redundant work, by also sorting a
+ * list of items to be checked using the internal sort criteria for objects
+ * (object pair keys), and then, when searching for the second or subsequent
+ * item, picking it up where we left off knowing that the second or subsequent
+ * item can not be at a point below the low bound set when the first was found.
+ * This is only useful for objects, not arrays (which have a user-defined
+ * order), so array superheader Jsonbs should just pass NULL. Moreover, it's
+ * only useful because we only match object pairs on the basis of their key, so
+ * presumably anyone exploiting this is only interested in matching Object keys
+ * with a String. lowbound is given in units of pairs, not underlying values.
+ */
+JsonbValue *
+findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 flags,
+ uint32 *lowbound, JsonbValue * key)
+{
+ uint32 superheader = *(uint32 *) sheader;
+ JEntry *array = (JEntry *) (sheader + sizeof(uint32));
+ int count = (superheader & JB_CMASK);
+ JsonbValue *result = palloc(sizeof(JsonbValue));
+
+ Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0);
+
+ if (flags & JB_FARRAY & superheader)
+ {
+ char *data = (char *) (array + (superheader & JB_CMASK));
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ JEntry *e = array + i;
+
+ if (JBE_ISNULL(*e) && key->type == jbvNull)
+ {
+ result->type = jbvNull;
+ result->estSize = sizeof(JEntry);
+ }
+ else if (JBE_ISSTRING(*e) && key->type == jbvString)
+ {
+ result->type = jbvString;
+ result->string.val = data + JBE_OFF(*e);
+ result->string.len = JBE_LEN(*e);
+ result->estSize = sizeof(JEntry) + result->string.len;
+ }
+ else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric)
+ {
+ result->type = jbvNumeric;
+ result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+ result->estSize = 2 * sizeof(JEntry) +
+ VARSIZE_ANY(result->numeric);
+ }
+ else if (JBE_ISBOOL(*e) && key->type == jbvBool)
+ {
+ result->type = jbvBool;
+ result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+ result->estSize = sizeof(JEntry);
+ }
+ else
+ continue;
+
+ if (compareJsonbScalarValue(key, result) == 0)
+ return result;
+ }
+ }
+ else if (flags & JB_FOBJECT & superheader)
+ {
+ /* Since this is an object, account for *Pairs* of Jentrys */
+ char *data = (char *) (array + (superheader & JB_CMASK) * 2);
+ uint32 stopLow = lowbound ? *lowbound : 0,
+ stopMiddle;
+
+ /* Object key past by caller must be a string */
+ Assert(key->type == jbvString);
+
+ /* Binary search on object/pair keys *only* */
+ while (stopLow < count)
+ {
+ JEntry *entry;
+ int difference;
+ JsonbValue candidate;
+
+ /*
+ * Note how we compensate for the fact that we're iterating through
+ * pairs (not entries) throughout.
+ */
+ stopMiddle = stopLow + (count - stopLow) / 2;
+
+ entry = array + stopMiddle * 2;
+
+ candidate.type = jbvString;
+ candidate.string.val = data + JBE_OFF(*entry);
+ candidate.string.len = JBE_LEN(*entry);
+ candidate.estSize = sizeof(JEntry) + candidate.string.len;
+
+ difference = lengthCompareJsonbStringValue(&candidate, key, NULL);
+
+ if (difference == 0)
+ {
+ /* Found our value (from key/value pair) */
+ JEntry *v = entry + 1;
+
+ if (lowbound)
+ *lowbound = stopMiddle + 1;
+
+ if (JBE_ISNULL(*v))
+ {
+ result->type = jbvNull;
+ result->estSize = sizeof(JEntry);
+ }
+ else if (JBE_ISSTRING(*v))
+ {
+ result->type = jbvString;
+ result->string.val = data + JBE_OFF(*v);
+ result->string.len = JBE_LEN(*v);
+ result->estSize = sizeof(JEntry) + result->string.len;
+ }
+ else if (JBE_ISNUMERIC(*v))
+ {
+ result->type = jbvNumeric;
+ result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v)));
+ result->estSize = 2 * sizeof(JEntry) +
+ VARSIZE_ANY(result->numeric);
+ }
+ else if (JBE_ISBOOL(*v))
+ {
+ result->type = jbvBool;
+ result->boolean = JBE_ISBOOL_TRUE(*v) != 0;
+ result->estSize = sizeof(JEntry);
+ }
+ else
+ {
+ /*
+ * See header comments to understand why this never happens
+ * with arrays
+ */
+ result->type = jbvBinary;
+ result->binary.data = data + INTALIGN(JBE_OFF(*v));
+ result->binary.len = JBE_LEN(*v) -
+ (INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v));
+ result->estSize = 2 * sizeof(JEntry) + result->binary.len;
+ }
+
+ return result;
+ }
+ else
+ {
+ if (difference < 0)
+ stopLow = stopMiddle + 1;
+ else
+ count = stopMiddle;
+ }
+ }
+
+ if (lowbound)
+ *lowbound = stopLow;
+ }
+
+ /* Not found */
+ pfree(result);
+ return NULL;
+}
+
+/*
+ * Get i-th value of Jsonb array from superheader.
+ *
+ * Returns palloc()'d copy of value.
+ */
+JsonbValue *
+getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, uint32 i)
+{
+ uint32 superheader = *(uint32 *) sheader;
+ JsonbValue *result;
+ JEntry *array,
+ *e;
+ char *data;
+
+ result = palloc(sizeof(JsonbValue));
+
+ if (i >= (superheader & JB_CMASK))
+ return NULL;
+
+ array = (JEntry *) (sheader + sizeof(uint32));
+
+ if (superheader & JB_FARRAY)
+ {
+ e = array + i;
+ data = (char *) (array + (superheader & JB_CMASK));
+ }
+ else
+ {
+ elog(ERROR, "not a jsonb array");
+ }
+
+ if (JBE_ISNULL(*e))
+ {
+ result->type = jbvNull;
+ result->estSize = sizeof(JEntry);
+ }
+ else if (JBE_ISSTRING(*e))
+ {
+ result->type = jbvString;
+ result->string.val = data + JBE_OFF(*e);
+ result->string.len = JBE_LEN(*e);
+ result->estSize = sizeof(JEntry) + result->string.len;
+ }
+ else if (JBE_ISNUMERIC(*e))
+ {
+ result->type = jbvNumeric;
+ result->numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e)));
+ result->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(result->numeric);
+ }
+ else if (JBE_ISBOOL(*e))
+ {
+ result->type = jbvBool;
+ result->boolean = JBE_ISBOOL_TRUE(*e) != 0;
+ result->estSize = sizeof(JEntry);
+ }
+ else
+ {
+ result->type = jbvBinary;
+ result->binary.data = data + INTALIGN(JBE_OFF(*e));
+ result->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e));
+ result->estSize = result->binary.len + 2 * sizeof(JEntry);
+ }
+
+ return result;
+}
+
+/*
+ * Push JsonbValue into JsonbParseState.
+ *
+ * Used when parsing JSON tokens to form Jsonb, or when converting an in-memory
+ * JsonbValue to a Jsonb.
+ *
+ * Initial state of *JsonbParseState is NULL, since it'll be allocated here
+ * originally (caller will get JsonbParseState back by reference).
+ *
+ * Only sequential tokens pertaining to non-container types should pass a
+ * JsonbValue. There is one exception -- WJB_BEGIN_ARRAY callers may pass a
+ * "raw scalar" pseudo array to append that.
+ */
+JsonbValue *
+pushJsonbValue(JsonbParseState ** pstate, int seq, JsonbValue * scalarVal)
+{
+ JsonbValue *result = NULL;
+
+ switch (seq)
+ {
+ case WJB_BEGIN_ARRAY:
+ Assert(!scalarVal || scalarVal->array.rawScalar);
+ *pstate = pushState(pstate);
+ result = &(*pstate)->contVal;
+ (*pstate)->contVal.type = jbvArray;
+ (*pstate)->contVal.estSize = 3 * sizeof(JEntry);
+ (*pstate)->contVal.array.nElems = 0;
+ (*pstate)->contVal.array.rawScalar = (scalarVal &&
+ scalarVal->array.rawScalar);
+ if (scalarVal && scalarVal->array.nElems > 0)
+ {
+ /* Assume that this array is still really a scalar */
+ Assert(scalarVal->type == jbvArray);
+ (*pstate)->size = scalarVal->array.nElems;
+ }
+ else
+ {
+ (*pstate)->size = 4;
+ }
+ (*pstate)->contVal.array.elems = palloc(sizeof(JsonbValue) *
+ (*pstate)->size);
+ break;
+ case WJB_BEGIN_OBJECT:
+ Assert(!scalarVal);
+ *pstate = pushState(pstate);
+ result = &(*pstate)->contVal;
+ (*pstate)->contVal.type = jbvObject;
+ (*pstate)->contVal.estSize = 3 * sizeof(JEntry);
+ (*pstate)->contVal.object.nPairs = 0;
+ (*pstate)->size = 4;
+ (*pstate)->contVal.object.pairs = palloc(sizeof(JsonbPair) *
+ (*pstate)->size);
+ break;
+ case WJB_KEY:
+ Assert(scalarVal->type == jbvString);
+ appendKey(*pstate, scalarVal);
+ break;
+ case WJB_VALUE:
+ Assert(IsAJsonbScalar(scalarVal) ||
+ scalarVal->type == jbvBinary);
+ appendValue(*pstate, scalarVal);
+ break;
+ case WJB_ELEM:
+ Assert(IsAJsonbScalar(scalarVal) ||
+ scalarVal->type == jbvBinary);
+ appendElement(*pstate, scalarVal);
+ break;
+ case WJB_END_OBJECT:
+ uniqueifyJsonbObject(&(*pstate)->contVal);
+ case WJB_END_ARRAY:
+ /* Steps here common to WJB_END_OBJECT case */
+ Assert(!scalarVal);
+ result = &(*pstate)->contVal;
+
+ /*
+ * Pop stack and push current array/object as value in parent
+ * array/object
+ */
+ *pstate = (*pstate)->next;
+ if (*pstate)
+ {
+ switch ((*pstate)->contVal.type)
+ {
+ case jbvArray:
+ appendElement(*pstate, result);
+ break;
+ case jbvObject:
+ appendValue(*pstate, result);
+ break;
+ default:
+ elog(ERROR, "invalid jsonb container type");
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonb sequential processing token");
+ }
+
+ return result;
+}
+
+/*
+ * Given a Jsonb superheader, expand to JsonbIterator to iterate over items
+ * fully expanded to in-memory representation for manipulation.
+ *
+ * See JsonbIteratorNext() for notes on memory management.
+ */
+JsonbIterator *
+JsonbIteratorInit(JsonbSuperHeader sheader)
+{
+ JsonbIterator *it = palloc(sizeof(JsonbIterator));
+
+ iteratorFromContainerBuf(it, sheader);
+ it->parent = NULL;
+
+ return it;
+}
+
+/*
+ * Get next JsonbValue while iterating
+ *
+ * Caller should initially pass their own, original iterator. They may get
+ * back a child iterator palloc()'d here instead. The function can be relied
+ * on to free those child iterators, lest the memory allocated for highly
+ * nested objects become unreasonable, but only if callers don't end iteration
+ * early (by breaking upon having found something in a search, for example).
+ *
+ * Callers in such a scenario, that are particularly sensitive to leaking
+ * memory in a long-lived context may walk the ancestral tree from the final
+ * iterator we left them with to its oldest ancestor, pfree()ing as they go.
+ * They do not have to free any other memory previously allocated for iterators
+ * but not accessible as direct ancestors of the iterator they're last passed
+ * back.
+ *
+ * Returns "Jsonb sequential processing" token value. Iterator "state"
+ * reflects the current stage of the process in a less granular fashion, and is
+ * mostly used here to track things internally with respect to particular
+ * iterators.
+ *
+ * Clients of this function should not have to handle any jbvBinary values
+ * (since recursive calls will deal with this), provided skipNested is false.
+ * It is our job to expand the jbvBinary representation without bothering them
+ * with it. However, clients should not take it upon themselves to touch array
+ * or Object element/pair buffers, since their element/pair pointers are
+ * garbage.
+ */
+int
+JsonbIteratorNext(JsonbIterator ** it, JsonbValue * val, bool skipNested)
+{
+ JsonbIterState state;
+
+ /* Guard against stack overflow due to overly complex Jsonb */
+ check_stack_depth();
+
+ /* Recursive caller may have original caller's iterator */
+ if (*it == NULL)
+ return WJB_DONE;
+
+ state = (*it)->state;
+
+ if ((*it)->containerType == JB_FARRAY)
+ {
+ if (state == jbi_start)
+ {
+ /* Set v to array on first array call */
+ val->type = jbvArray;
+ val->array.nElems = (*it)->nElems;
+ /*
+ * v->array.elems is not actually set, because we aren't doing a
+ * full conversion
+ */
+ val->array.rawScalar = (*it)->isScalar;
+ (*it)->i = 0;
+ /* Set state for next call */
+ (*it)->state = jbi_elem;
+ return WJB_BEGIN_ARRAY;
+ }
+ else if (state == jbi_elem)
+ {
+ if ((*it)->i >= (*it)->nElems)
+ {
+ /*
+ * All elements within array already processed. Report this to
+ * caller, and give it back original parent iterator (which
+ * independently tracks iteration progress at its level of
+ * nesting).
+ */
+ *it = freeAndGetParent(*it);
+ return WJB_END_ARRAY;
+ }
+ else if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i++],
+ skipNested))
+ {
+ /*
+ * New child iterator acquired within formIterIsContainer.
+ * Recurse into container. Don't directly return jbvBinary
+ * value to top-level client.
+ */
+ return JsonbIteratorNext(it, val, skipNested);
+ }
+ else
+ {
+ /* Scalar item in array */
+ return WJB_ELEM;
+ }
+ }
+ }
+ else if ((*it)->containerType == JB_FOBJECT)
+ {
+ if (state == jbi_start)
+ {
+ /* Set v to object on first object call */
+ val->type = jbvObject;
+ val->object.nPairs = (*it)->nElems;
+ /*
+ * v->object.pairs is not actually set, because we aren't doing a
+ * full conversion
+ */
+ (*it)->i = 0;
+ /* Set state for next call */
+ (*it)->state = jbi_key;
+ return WJB_BEGIN_OBJECT;
+ }
+ else if (state == jbi_key)
+ {
+ if ((*it)->i >= (*it)->nElems)
+ {
+ /*
+ * All pairs within object already processed. Report this to
+ * caller, and give it back original containing iterator (which
+ * independently tracks iteration progress at its level of
+ * nesting).
+ */
+ *it = freeAndGetParent(*it);
+ return WJB_END_OBJECT;
+ }
+ else
+ {
+ /*
+ * Return binary item key (ensured by setting skipNested to
+ * false directly). No child iterator, no further recursion.
+ * When control reaches here, it's probably from a recursive
+ * call.
+ */
+ if (formIterIsContainer(it, val, &(*it)->meta[(*it)->i * 2], false))
+ elog(ERROR, "unexpected container as object key");
+
+ Assert(val->type == jbvString);
+ /* Set state for next call */
+ (*it)->state = jbi_value;
+ return WJB_KEY;
+ }
+ }
+ else if (state == jbi_value)
+ {
+ /* Set state for next call */
+ (*it)->state = jbi_key;
+
+ /*
+ * Value may be a container, in which case we recurse with new,
+ * child iterator. If it is, don't bother !skipNested callers with
+ * dealing with the jbvBinary representation.
+ */
+ if (formIterIsContainer(it, val, &(*it)->meta[((*it)->i++) * 2 + 1],
+ skipNested))
+ return JsonbIteratorNext(it, val, skipNested);
+ else
+ return WJB_VALUE;
+ }
+ }
+
+ elog(ERROR, "invalid iterator state");
+}
+
+/*
+ * Worker for "contains" operator's function
+ *
+ * Formally speaking, containment is top-down, unordered subtree isomorphism.
+ *
+ * Takes iterators that belong to some container type. These iterators
+ * "belong" to those values in the sense that they've just been initialized in
+ * respect of them by the caller (perhaps in a nested fashion).
+ *
+ * "val" is lhs Jsonb, and mContained is rhs Jsonb when called from top level.
+ * We determine if mContained is contained within val.
+ */
+bool
+JsonbDeepContains(JsonbIterator ** val, JsonbIterator ** mContained)
+{
+ uint32 rval,
+ rcont;
+ JsonbValue vval,
+ vcontained;
+ /*
+ * Guard against stack overflow due to overly complex Jsonb.
+ *
+ * Functions called here independently take this precaution, but that might
+ * not be sufficient since this is also a recursive function.
+ */
+ check_stack_depth();
+
+ rval = JsonbIteratorNext(val, &vval, false);
+ rcont = JsonbIteratorNext(mContained, &vcontained, false);
+
+ if (rval != rcont)
+ {
+ /*
+ * The differing return values can immediately be taken as indicating
+ * two differing container types at this nesting level, which is
+ * sufficient reason to give up entirely (but it should be the case
+ * that they're both some container type).
+ */
+ Assert(rval == WJB_BEGIN_OBJECT || rval == WJB_BEGIN_ARRAY);
+ Assert(rcont == WJB_BEGIN_OBJECT || rcont == WJB_BEGIN_ARRAY);
+ return false;
+ }
+ else if (rcont == WJB_BEGIN_OBJECT)
+ {
+ JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */
+
+ Assert(vcontained.type == jbvObject);
+
+ /* Work through rhs "is it contained within?" object */
+ for (;;)
+ {
+ rcont = JsonbIteratorNext(mContained, &vcontained, false);
+
+ /*
+ * When we get through caller's rhs "is it contained within?"
+ * object without failing to find one of its values, it's
+ * contained.
+ */
+ if (rcont == WJB_END_OBJECT)
+ return true;
+
+ Assert(rcont == WJB_KEY);
+
+ /* First, find value by key... */
+ lhsVal = findJsonbValueFromSuperHeader((*val)->buffer,
+ JB_FOBJECT,
+ NULL,
+ &vcontained);
+
+ if (!lhsVal)
+ return false;
+
+ /*
+ * ...at this stage it is apparent that there is at least a key
+ * match for this rhs pair.
+ */
+ rcont = JsonbIteratorNext(mContained, &vcontained, true);
+
+ Assert(rcont == WJB_VALUE);
+
+ /*
+ * Compare rhs pair's value with lhs pair's value just found using
+ * key
+ */
+ if (lhsVal->type != vcontained.type)
+ {
+ return false;
+ }
+ else if (IsAJsonbScalar(lhsVal))
+ {
+ if (compareJsonbScalarValue(lhsVal, &vcontained) != 0)
+ return false;
+ }
+ else
+ {
+ /* Nested container value (object or array) */
+ JsonbIterator *nestval, *nestContained;
+
+ Assert(lhsVal->type == jbvBinary);
+ Assert(vcontained.type == jbvBinary);
+
+ nestval = JsonbIteratorInit(lhsVal->binary.data);
+ nestContained = JsonbIteratorInit(vcontained.binary.data);
+
+ /*
+ * Match "value" side of rhs datum object's pair recursively.
+ * It's a nested structure.
+ *
+ * Note that nesting still has to "match up" at the right
+ * nesting sub-levels. However, there need only be zero or
+ * more matching pairs (or elements) at each nesting level
+ * (provided the *rhs* pairs/elements *all* match on each
+ * level), which enables searching nested structures for a
+ * single String or other primitive type sub-datum quite
+ * effectively (provided the user constructed the rhs nested
+ * structure such that we "know where to look").
+ *
+ * In other words, the mapping of container nodes in the rhs
+ * "vcontained" Jsonb to internal nodes on the lhs is
+ * injective, and parent-child edges on the rhs must be mapped
+ * to parent-child edges on the lhs to satisfy the condition of
+ * containment (plus of course the mapped nodes must be equal).
+ */
+ if (!JsonbDeepContains(&nestval, &nestContained))
+ return false;
+ }
+ }
+ }
+ else if (rcont == WJB_BEGIN_ARRAY)
+ {
+ JsonbValue *lhsConts = NULL;
+ uint32 nLhsElems = vval.array.nElems;
+
+ Assert(vcontained.type == jbvArray);
+
+ /*
+ * Handle distinction between "raw scalar" pseudo arrays, and real
+ * arrays.
+ *
+ * A raw scalar may contain another raw scalar, and an array may
+ * contain a raw scalar, but a raw scalar may not contain an array. We
+ * don't do something like this for the object case, since objects can
+ * only contain pairs, never raw scalars (a pair is represented by an
+ * rhs object argument with a single contained pair).
+ */
+ if (vval.array.rawScalar && !vcontained.array.rawScalar)
+ return false;
+
+ /* Work through rhs "is it contained within?" array */
+ for (;;)
+ {
+ rcont = JsonbIteratorNext(mContained, &vcontained, true);
+
+ /*
+ * When we get through caller's rhs "is it contained within?" array
+ * without failing to find one of its values, it's contained.
+ */
+ if (rcont == WJB_END_ARRAY)
+ return true;
+
+ Assert(rcont == WJB_ELEM);
+
+ if (IsAJsonbScalar(&vcontained))
+ {
+ if (!findJsonbValueFromSuperHeader((*val)->buffer,
+ JB_FARRAY,
+ NULL,
+ &vcontained))
+ return false;
+ }
+ else
+ {
+ uint32 i;
+
+ /*
+ * If this is first container found in rhs array (at this
+ * depth), initialize temp lhs array of containers
+ */
+ if (lhsConts == NULL)
+ {
+ uint32 j = 0;
+
+ /* Make room for all possible values */
+ lhsConts = palloc(sizeof(JsonbValue) * nLhsElems);
+
+ for (i = 0; i < nLhsElems; i++)
+ {
+ /* Store all lhs elements in temp array*/
+ rcont = JsonbIteratorNext(val, &vval, true);
+ Assert(rcont == WJB_ELEM);
+
+ if (vval.type == jbvBinary)
+ lhsConts[j++] = vval;
+ }
+
+ /* No container elements in temp array, so give up now */
+ if (j == 0)
+ return false;
+
+ /* We may have only partially filled array */
+ nLhsElems = j;
+ }
+
+ /* XXX: Nested array containment is O(N^2) */
+ for (i = 0; i < nLhsElems; i++)
+ {
+ /* Nested container value (object or array) */
+ JsonbIterator *nestval, *nestContained;
+ bool contains;
+
+ nestval = JsonbIteratorInit(lhsConts[i].binary.data);
+ nestContained = JsonbIteratorInit(vcontained.binary.data);
+
+ contains = JsonbDeepContains(&nestval, &nestContained);
+
+ if (nestval)
+ pfree(nestval);
+ if (nestContained)
+ pfree(nestContained);
+ if (contains)
+ break;
+ }
+
+ /*
+ * Report rhs container value is not contained if couldn't
+ * match rhs container to *some* lhs cont
+ */
+ if (i == nLhsElems)
+ return false;
+ }
+ }
+ }
+ else
+ {
+ elog(ERROR, "invalid jsonb container type");
+ }
+
+ elog(ERROR, "unexpectedly fell off end of jsonb container");
+}
+
+/*
+ * Convert a Postgres text array to a Jsonb array, sorted and with
+ * de-duplicated key elements. This is used for searching an object for items
+ * in the array, so we enforce that the number of strings cannot exceed
+ * JSONB_MAX_PAIRS.
+ */
+JsonbValue *
+arrayToJsonbSortedArray(ArrayType *array)
+{
+ Datum *key_datums;
+ bool *key_nulls;
+ int elem_count;
+ JsonbValue *result;
+ int i,
+ j;
+
+ /* Extract data for sorting */
+ deconstruct_array(array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls,
+ &elem_count);
+
+ if (elem_count == 0)
+ return NULL;
+
+ /*
+ * A text array uses at least eight bytes per element, so any overflow in
+ * "key_count * sizeof(JsonbPair)" is small enough for palloc() to catch.
+ * However, credible improvements to the array format could invalidate that
+ * assumption. Therefore, use an explicit check rather than relying on
+ * palloc() to complain.
+ */
+ if (elem_count > JSONB_MAX_PAIRS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array elements (%d) exceeds maximum allowed Jsonb pairs (%zu)",
+ elem_count, JSONB_MAX_PAIRS)));
+
+ result = palloc(sizeof(JsonbValue));
+ result->type = jbvArray;
+ result->array.rawScalar = false;
+ result->array.elems = palloc(sizeof(JsonbPair) * elem_count);
+
+ for (i = 0, j = 0; i < elem_count; i++)
+ {
+ if (!key_nulls[i])
+ {
+ result->array.elems[j].type = jbvString;
+ result->array.elems[j].string.val = VARDATA(key_datums[i]);
+ result->array.elems[j].string.len = VARSIZE(key_datums[i]) - VARHDRSZ;
+ j++;
+ }
+ }
+ result->array.nElems = j;
+
+ uniqueifyJsonbArray(result);
+ return result;
+}
+
+/*
+ * Hash a JsonbValue scalar value, mixing in the hash value with an existing
+ * hash provided by the caller.
+ *
+ * Some callers may wish to independently XOR in JB_FOBJECT and JB_FARRAY
+ * flags.
+ */
+void
+JsonbHashScalarValue(const JsonbValue * scalarVal, uint32 * hash)
+{
+ int tmp;
+
+ /*
+ * Combine hash values of successive keys, values and elements by rotating
+ * the previous value left 1 bit, then XOR'ing in the new
+ * key/value/element's hash value.
+ */
+ *hash = (*hash << 1) | (*hash >> 31);
+ switch (scalarVal->type)
+ {
+ case jbvNull:
+ *hash ^= 0x01;
+ return;
+ case jbvString:
+ tmp = hash_any((unsigned char *) scalarVal->string.val,
+ scalarVal->string.len);
+ *hash ^= tmp;
+ return;
+ case jbvNumeric:
+ /* Must be unaffected by trailing zeroes */
+ tmp = DatumGetInt32(DirectFunctionCall1(hash_numeric,
+ NumericGetDatum(scalarVal->numeric)));
+ *hash ^= tmp;
+ return;
+ case jbvBool:
+ *hash ^= scalarVal->boolean? 0x02:0x04;
+ return;
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ }
+}
+
+/*
+ * Are two scalar JsonbValues of the same type a and b equal?
+ *
+ * Does not use lexical comparisons. Therefore, it is essentially that this
+ * never be used against Strings for anything other than searching for values
+ * within a single jsonb.
+ */
+static int
+compareJsonbScalarValue(JsonbValue * aScalar, JsonbValue * bScalar)
+{
+ if (aScalar->type == bScalar->type)
+ {
+ switch (aScalar->type)
+ {
+ case jbvNull:
+ return 0;
+ case jbvString:
+ return lengthCompareJsonbStringValue(aScalar, bScalar, NULL);
+ case jbvNumeric:
+ return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+ PointerGetDatum(aScalar->numeric),
+ PointerGetDatum(bScalar->numeric)));
+ case jbvBool:
+ if (aScalar->boolean != bScalar->boolean)
+ return (aScalar->boolean > bScalar->boolean) ? 1 : -1;
+ else
+ return 0;
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ }
+ }
+ elog(ERROR, "jsonb scalar type mismatch");
+}
+
+/*
+ * Standard lexical qsort() comparator of jsonb strings.
+ *
+ * Sorts strings lexically, using the default database collation. Used by
+ * B-Tree operators, where a lexical sort order is generally expected.
+ */
+static int
+lexicalCompareJsonbStringValue(const void *a, const void *b)
+{
+ const JsonbValue *va = (const JsonbValue *) a;
+ const JsonbValue *vb = (const JsonbValue *) b;
+
+ Assert(va->type == jbvString);
+ Assert(vb->type == jbvString);
+
+ return varstr_cmp(va->string.val, va->string.len, vb->string.val,
+ vb->string.len, DEFAULT_COLLATION_OID);
+}
+
+/*
+ * Given a JsonbValue, convert to Jsonb and store in preallocated Jsonb buffer
+ * sufficiently large to fit the value
+ */
+static Size
+convertJsonb(JsonbValue * val, Jsonb *buffer)
+{
+ convertState state;
+ Size len;
+
+ /* Should not already have binary representation */
+ Assert(val->type != jbvBinary);
+
+ state.buffer = buffer;
+ /* Start from superheader */
+ state.ptr = VARDATA(state.buffer);
+ state.levelSz = 8;
+ state.allState = palloc(sizeof(convertLevel) * state.levelSz);
+
+ walkJsonbValueConversion(val, &state, 0);
+
+ len = state.ptr - VARDATA(state.buffer);
+
+ Assert(len <= val->estSize);
+ return len;
+}
+
+/*
+ * Walk the tree representation of Jsonb, as part of the process of converting
+ * a JsonbValue to a Jsonb.
+ *
+ * This high-level function takes care of recursion into sub-containers, but at
+ * the top level calls putJsonbValueConversion once per sequential processing
+ * token (in a manner similar to generic iteration).
+ */
+static void
+walkJsonbValueConversion(JsonbValue * val, convertState * cstate,
+ uint32 nestlevel)
+{
+ int i;
+
+ check_stack_depth();
+
+ if (!val)
+ return;
+
+ switch (val->type)
+ {
+ case jbvArray:
+
+ putJsonbValueConversion(cstate, val, WJB_BEGIN_ARRAY, nestlevel);
+ for (i = 0; i < val->array.nElems; i++)
+ {
+ if (IsAJsonbScalar(&val->array.elems[i]) ||
+ val->array.elems[i].type == jbvBinary)
+ putJsonbValueConversion(cstate, val->array.elems + i,
+ WJB_ELEM, nestlevel);
+ else
+ walkJsonbValueConversion(val->array.elems + i, cstate,
+ nestlevel + 1);
+ }
+ putJsonbValueConversion(cstate, val, WJB_END_ARRAY, nestlevel);
+
+ break;
+ case jbvObject:
+
+ putJsonbValueConversion(cstate, val, WJB_BEGIN_OBJECT, nestlevel);
+ for (i = 0; i < val->object.nPairs; i++)
+ {
+ putJsonbValueConversion(cstate, &val->object.pairs[i].key,
+ WJB_KEY, nestlevel);
+
+ if (IsAJsonbScalar(&val->object.pairs[i].value) ||
+ val->object.pairs[i].value.type == jbvBinary)
+ putJsonbValueConversion(cstate,
+ &val->object.pairs[i].value,
+ WJB_VALUE, nestlevel);
+ else
+ walkJsonbValueConversion(&val->object.pairs[i].value,
+ cstate, nestlevel + 1);
+ }
+ putJsonbValueConversion(cstate, val, WJB_END_OBJECT, nestlevel);
+
+ break;
+ default:
+ elog(ERROR, "unknown type of jsonb container");
+ }
+}
+
+/*
+ * walkJsonbValueConversion() worker. Add padding sufficient to int-align our
+ * access to conversion buffer.
+ */
+static inline
+short addPaddingInt(convertState * cstate)
+{
+ short padlen, p;
+
+ padlen = INTALIGN(cstate->ptr - VARDATA(cstate->buffer)) -
+ (cstate->ptr - VARDATA(cstate->buffer));
+
+ for (p = padlen; p > 0; p--)
+ {
+ *cstate->ptr = '\0';
+ cstate->ptr++;
+ }
+
+ return padlen;
+}
+
+/*
+ * walkJsonbValueConversion() worker.
+ *
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * copy over an arbitrary individual JsonbValue. This function may copy any
+ * type of value, even containers (Objects/arrays). However, it is not
+ * responsible for recursive aspects of walking the tree (so only top-level
+ * Object/array details are handled).
+ *
+ * No details about their keys/values/elements are handled recursively -
+ * rather, the function is called as required for the start of an Object/Array,
+ * and the end (i.e. there is one call per sequential processing WJB_* token).
+ */
+static void
+putJsonbValueConversion(convertState * cstate, JsonbValue * val, uint32 flags,
+ uint32 level)
+{
+ if (level == cstate->levelSz)
+ {
+ cstate->levelSz *= 2;
+ cstate->allState = repalloc(cstate->allState,
+ sizeof(convertLevel) * cstate->levelSz);
+ }
+
+ cstate->contPtr = cstate->allState + level;
+
+ if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT))
+ {
+ Assert(((flags & WJB_BEGIN_ARRAY) && val->type == jbvArray) ||
+ ((flags & WJB_BEGIN_OBJECT) && val->type == jbvObject));
+
+ /* Initialize pointer into conversion buffer at this level */
+ cstate->contPtr->begin = cstate->ptr;
+
+ addPaddingInt(cstate);
+
+ /* Initialize everything else at this level */
+ cstate->contPtr->header = (uint32 *) cstate->ptr;
+ /* Advance past header */
+ cstate->ptr += sizeof(uint32);
+ cstate->contPtr->meta = (JEntry *) cstate->ptr;
+ cstate->contPtr->i = 0;
+
+ if (val->type == jbvArray)
+ {
+ *cstate->contPtr->header = val->array.nElems | JB_FARRAY;
+ cstate->ptr += sizeof(JEntry) * val->array.nElems;
+
+ if (val->array.rawScalar)
+ {
+ Assert(val->array.nElems == 1);
+ Assert(level == 0);
+ *cstate->contPtr->header |= JB_FSCALAR;
+ }
+ }
+ else
+ {
+ *cstate->contPtr->header = val->object.nPairs | JB_FOBJECT;
+ cstate->ptr += sizeof(JEntry) * val->object.nPairs * 2;
+ }
+ }
+ else if (flags & WJB_ELEM)
+ {
+ putScalarConversion(cstate, val, level, cstate->contPtr->i);
+ cstate->contPtr->i++;
+ }
+ else if (flags & WJB_KEY)
+ {
+ Assert(val->type == jbvString);
+
+ putScalarConversion(cstate, val, level, cstate->contPtr->i * 2);
+ }
+ else if (flags & WJB_VALUE)
+ {
+ putScalarConversion(cstate, val, level, cstate->contPtr->i * 2 + 1);
+ cstate->contPtr->i++;
+ }
+ else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT))
+ {
+ convertLevel *prevPtr; /* Prev container pointer */
+ uint32 len,
+ i;
+
+ Assert(((flags & WJB_END_ARRAY) && val->type == jbvArray) ||
+ ((flags & WJB_END_OBJECT) && val->type == jbvObject));
+
+ if (level == 0)
+ return;
+
+ len = cstate->ptr - (char *) cstate->contPtr->begin;
+
+ prevPtr = cstate->contPtr - 1;
+
+ if (*prevPtr->header & JB_FARRAY)
+ {
+ i = prevPtr->i;
+
+ prevPtr->meta[i].header = JENTRY_ISNEST;
+
+ if (i == 0)
+ prevPtr->meta[0].header |= JENTRY_ISFIRST | len;
+ else
+ prevPtr->meta[i].header |=
+ (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
+ }
+ else if (*prevPtr->header & JB_FOBJECT)
+ {
+ i = 2 * prevPtr->i + 1; /* Value, not key */
+
+ prevPtr->meta[i].header = JENTRY_ISNEST;
+
+ prevPtr->meta[i].header |=
+ (prevPtr->meta[i - 1].header & JENTRY_POSMASK) + len;
+ }
+ else
+ {
+ elog(ERROR, "invalid jsonb container type");
+ }
+
+ Assert(cstate->ptr - cstate->contPtr->begin <= val->estSize);
+ prevPtr->i++;
+ }
+ else
+ {
+ elog(ERROR, "unknown flag encountered during jsonb tree walk");
+ }
+}
+
+/*
+ * As part of the process of converting an arbitrary JsonbValue to a Jsonb,
+ * serialize and copy a scalar value into buffer.
+ *
+ * This is a worker function for putJsonbValueConversion() (itself a worker for
+ * walkJsonbValueConversion()). It handles the details with regard to Jentry
+ * metadata peculiar to each scalar type.
+ */
+static void
+putScalarConversion(convertState * cstate, JsonbValue * scalarVal, uint32 level,
+ uint32 i)
+{
+ int numlen;
+ short padlen;
+
+ cstate->contPtr = cstate->allState + level;
+
+ if (i == 0)
+ cstate->contPtr->meta[0].header = JENTRY_ISFIRST;
+ else
+ cstate->contPtr->meta[i].header = 0;
+
+ switch (scalarVal->type)
+ {
+ case jbvNull:
+ cstate->contPtr->meta[i].header |= JENTRY_ISNULL;
+
+ if (i > 0)
+ cstate->contPtr->meta[i].header |=
+ cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+ break;
+ case jbvString:
+ memcpy(cstate->ptr, scalarVal->string.val, scalarVal->string.len);
+ cstate->ptr += scalarVal->string.len;
+
+ if (i == 0)
+ cstate->contPtr->meta[0].header |= scalarVal->string.len;
+ else
+ cstate->contPtr->meta[i].header |=
+ (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK) +
+ scalarVal->string.len;
+ break;
+ case jbvNumeric:
+ numlen = VARSIZE_ANY(scalarVal->numeric);
+ padlen = addPaddingInt(cstate);
+
+ memcpy(cstate->ptr, scalarVal->numeric, numlen);
+ cstate->ptr += numlen;
+
+ cstate->contPtr->meta[i].header |= JENTRY_ISNUMERIC;
+ if (i == 0)
+ cstate->contPtr->meta[0].header |= padlen + numlen;
+ else
+ cstate->contPtr->meta[i].header |=
+ (cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK)
+ + padlen + numlen;
+ break;
+ case jbvBool:
+ cstate->contPtr->meta[i].header |= (scalarVal->boolean) ?
+ JENTRY_ISTRUE : JENTRY_ISFALSE;
+
+ if (i > 0)
+ cstate->contPtr->meta[i].header |=
+ cstate->contPtr->meta[i - 1].header & JENTRY_POSMASK;
+ break;
+ default:
+ elog(ERROR, "invalid jsonb scalar type");
+ }
+}
+
+/*
+ * Given superheader pointer into buffer, initialize iterator. Must be a
+ * container type.
+ */
+static void
+iteratorFromContainerBuf(JsonbIterator * it, JsonbSuperHeader sheader)
+{
+ uint32 superheader = *(uint32 *) sheader;
+
+ it->containerType = superheader & (JB_FARRAY | JB_FOBJECT);
+ it->nElems = superheader & JB_CMASK;
+ it->buffer = sheader;
+
+ /* Array starts just after header */
+ it->meta = (JEntry *) (sheader + sizeof(uint32));
+ it->state = jbi_start;
+
+ switch (it->containerType)
+ {
+ case JB_FARRAY:
+ it->dataProper =
+ (char *) it->meta + it->nElems * sizeof(JEntry);
+ it->isScalar = (superheader & JB_FSCALAR) != 0;
+ /* This is either a "raw scalar", or an array */
+ Assert(!it->isScalar || it->nElems == 1);
+ break;
+ case JB_FOBJECT:
+ /*
+ * Offset reflects that nElems indicates JsonbPairs in an object.
+ * Each key and each value contain Jentry metadata just the same.
+ */
+ it->dataProper =
+ (char *) it->meta + it->nElems * sizeof(JEntry) * 2;
+ break;
+ default:
+ elog(ERROR, "unknown type of jsonb container");
+ }
+}
+
+/*
+ * JsonbIteratorNext() worker
+ *
+ * Returns bool indicating if v was a non-jbvBinary container, and thus if
+ * further recursion is required by caller (according to its skipNested
+ * preference). If it is required, we set the caller's iterator for further
+ * recursion into the nested value. If we're going to skip nested items, just
+ * set v to a jbvBinary value, but don't set caller's iterator.
+ *
+ * Unlike with containers (either in this function or in any
+ * JsonbIteratorNext() infrastructure), we fully convert from what is
+ * ultimately a Jsonb on-disk representation, to a JsonbValue in-memory
+ * representation (for scalar values only). JsonbIteratorNext() initializes
+ * container Jsonbvalues, but without a sane private buffer. For scalar values
+ * it has to be done for real (even if we don't actually allocate more memory
+ * to do this. The point is that our JsonbValues scalars can be passed around
+ * anywhere).
+ */
+static bool
+formIterIsContainer(JsonbIterator ** it, JsonbValue * val, JEntry * ent,
+ bool skipNested)
+{
+ if (JBE_ISNULL(*ent))
+ {
+ val->type = jbvNull;
+ val->estSize = sizeof(JEntry);
+
+ return false;
+ }
+ else if (JBE_ISSTRING(*ent))
+ {
+ val->type = jbvString;
+ val->string.val = (*it)->dataProper + JBE_OFF(*ent);
+ val->string.len = JBE_LEN(*ent);
+ val->estSize = sizeof(JEntry) + val->string.len;
+
+ return false;
+ }
+ else if (JBE_ISNUMERIC(*ent))
+ {
+ val->type = jbvNumeric;
+ val->numeric = (Numeric) ((*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+ val->estSize = 2 * sizeof(JEntry) + VARSIZE_ANY(val->numeric);
+
+ return false;
+ }
+ else if (JBE_ISBOOL(*ent))
+ {
+ val->type = jbvBool;
+ val->boolean = JBE_ISBOOL_TRUE(*ent) != 0;
+ val->estSize = sizeof(JEntry);
+
+ return false;
+ }
+ else if (skipNested)
+ {
+ val->type = jbvBinary;
+ val->binary.data = (*it)->dataProper + INTALIGN(JBE_OFF(*ent));
+ val->binary.len = JBE_LEN(*ent) - (INTALIGN(JBE_OFF(*ent)) - JBE_OFF(*ent));
+ val->estSize = val->binary.len + 2 * sizeof(JEntry);
+
+ return false;
+ }
+ else
+ {
+ /*
+ * Must be container type, so setup caller's iterator to point to that,
+ * and return indication of that.
+ *
+ * Get child iterator.
+ */
+ JsonbIterator *child = palloc(sizeof(JsonbIterator));
+
+ iteratorFromContainerBuf(child,
+ (*it)->dataProper + INTALIGN(JBE_OFF(*ent)));
+
+ child->parent = *it;
+ *it = child;
+
+ return true;
+ }
+}
+
+/*
+ * JsonbIteratorNext() worker: Return parent, while freeing memory for current
+ * iterator
+ */
+static JsonbIterator *
+freeAndGetParent(JsonbIterator * it)
+{
+ JsonbIterator *v = it->parent;
+
+ pfree(it);
+ return v;
+}
+
+/*
+ * pushJsonbValue() worker: Iteration-like forming of Jsonb
+ */
+static JsonbParseState *
+pushState(JsonbParseState ** pstate)
+{
+ JsonbParseState *ns = palloc(sizeof(JsonbParseState));
+
+ ns->next = *pstate;
+ return ns;
+}
+
+/*
+ * pushJsonbValue() worker: Append a pair key to state when generating a Jsonb
+ */
+static void
+appendKey(JsonbParseState * pstate, JsonbValue * string)
+{
+ JsonbValue *object = &pstate->contVal;
+
+ Assert(object->type == jbvObject);
+ Assert(string->type == jbvString);
+
+ if (object->object.nPairs >= JSONB_MAX_PAIRS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of jsonb object pairs exceeds the maximum allowed (%zu)",
+ JSONB_MAX_PAIRS)));
+
+ if (object->object.nPairs >= pstate->size)
+ {
+ pstate->size *= 2;
+ object->object.pairs = repalloc(object->object.pairs,
+ sizeof(JsonbPair) * pstate->size);
+ }
+
+ object->object.pairs[object->object.nPairs].key = *string;
+ object->object.pairs[object->object.nPairs].order = object->object.nPairs;
+
+ object->estSize += string->estSize;
+}
+
+/*
+ * pushJsonbValue() worker: Append a pair value to state when generating a
+ * Jsonb
+ */
+static void
+appendValue(JsonbParseState * pstate, JsonbValue * scalarVal)
+{
+ JsonbValue *object = &pstate->contVal;
+
+ Assert(object->type == jbvObject);
+
+ object->object.pairs[object->object.nPairs++].value = *scalarVal;
+ object->estSize += scalarVal->estSize;
+}
+
+/*
+ * pushJsonbValue() worker: Append an element to state when generating a Jsonb
+ */
+static void
+appendElement(JsonbParseState * pstate, JsonbValue * scalarVal)
+{
+ JsonbValue *array = &pstate->contVal;
+
+ Assert(array->type == jbvArray);
+
+ if (array->array.nElems >= JSONB_MAX_ELEMS)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of jsonb array elements exceeds the maximum allowed (%zu)",
+ JSONB_MAX_ELEMS)));
+
+ if (array->array.nElems >= pstate->size)
+ {
+ pstate->size *= 2;
+ array->array.elems = repalloc(array->array.elems,
+ sizeof(JsonbValue) * pstate->size);
+ }
+
+ array->array.elems[array->array.nElems++] = *scalarVal;
+ array->estSize += scalarVal->estSize;
+}
+
+/*
+ * Compare two jbvString JsonbValue values, a and b.
+ *
+ * This is a special qsort_arg() comparator used to sort strings in certain
+ * internal contexts where it is sufficient to have a well-defined sort order.
+ * In particular, object pair keys are sorted according to this criteria to
+ * facilitate cheap binary searches where we don't care about lexical sort
+ * order.
+ *
+ * a and b are first sorted based on their length. If a tie-breaker is
+ * required, only then do we consider string binary equality.
+ *
+ * Third argument 'binequal' may point to a bool. If it's set, *binequal is set
+ * to true iff a and b have full binary equality, since some callers have an
+ * interest in whether the two values are equal or merely equivalent.
+ */
+static int
+lengthCompareJsonbStringValue(const void *a, const void *b, void *binequal)
+{
+ const JsonbValue *va = (const JsonbValue *) a;
+ const JsonbValue *vb = (const JsonbValue *) b;
+ int res;
+
+ Assert(va->type == jbvString);
+ Assert(vb->type == jbvString);
+
+ if (va->string.len == vb->string.len)
+ {
+ res = memcmp(va->string.val, vb->string.val, va->string.len);
+ if (res == 0 && binequal)
+ *((bool *) binequal) = true;
+ }
+ else
+ {
+ res = (va->string.len > vb->string.len) ? 1 : -1;
+ }
+
+ return res;
+}
+
+/*
+ * qsort_arg() comparator to compare JsonbPair values.
+ *
+ * Function implemented in terms of lengthCompareJsonbStringValue(), and thus the
+ * same "arg setting" hack will be applied here in respect of the pair's key
+ * values.
+ *
+ * N.B: String comparisons here are "length-wise"
+ *
+ * Pairs with equals keys are ordered such that the order field is respected.
+ */
+static int
+lengthCompareJsonbPair(const void *a, const void *b, void *binequal)
+{
+ const JsonbPair *pa = (const JsonbPair *) a;
+ const JsonbPair *pb = (const JsonbPair *) b;
+ int res;
+
+ res = lengthCompareJsonbStringValue(&pa->key, &pb->key, binequal);
+
+ /*
+ * Guarantee keeping order of equal pair. Unique algorithm will prefer
+ * first element as value.
+ */
+ if (res == 0)
+ res = (pa->order > pb->order) ? -1 : 1;
+
+ return res;
+}
+
+/*
+ * Sort and unique-ify pairs in JsonbValue object
+ */
+static void
+uniqueifyJsonbObject(JsonbValue * object)
+{
+ bool hasNonUniq = false;
+
+ Assert(object->type == jbvObject);
+
+ if (object->object.nPairs > 1)
+ qsort_arg(object->object.pairs, object->object.nPairs, sizeof(JsonbPair),
+ lengthCompareJsonbPair, &hasNonUniq);
+
+ if (hasNonUniq)
+ {
+ JsonbPair *ptr = object->object.pairs + 1,
+ *res = object->object.pairs;
+
+ while (ptr - object->object.pairs < object->object.nPairs)
+ {
+ /* Avoid copying over duplicate */
+ if (lengthCompareJsonbStringValue(ptr, res, NULL) == 0)
+ {
+ object->estSize -= ptr->key.estSize + ptr->value.estSize;
+ }
+ else
+ {
+ res++;
+ if (ptr != res)
+ memcpy(res, ptr, sizeof(JsonbPair));
+ }
+ ptr++;
+ }
+
+ object->object.nPairs = res + 1 - object->object.pairs;
+ }
+}
+
+/*
+ * Sort and unique-ify JsonbArray.
+ *
+ * Sorting uses internal ordering.
+ */
+static void
+uniqueifyJsonbArray(JsonbValue * array)
+{
+ bool hasNonUniq = false;
+
+ Assert(array->type == jbvArray);
+
+ /*
+ * Actually sort values, determining if any were equal on the basis of full
+ * binary equality (rather than just having the same string length).
+ */
+ if (array->array.nElems > 1)
+ qsort_arg(array->array.elems, array->array.nElems,
+ sizeof(JsonbValue), lengthCompareJsonbStringValue,
+ &hasNonUniq);
+
+ if (hasNonUniq)
+ {
+ JsonbValue *ptr = array->array.elems + 1,
+ *res = array->array.elems;
+
+ while (ptr - array->array.elems < array->array.nElems)
+ {
+ /* Avoid copying over duplicate */
+ if (lengthCompareJsonbStringValue(ptr, res, NULL) != 0)
+ {
+ res++;
+ *res = *ptr;
+ }
+
+ ptr++;
+ }
+
+ array->array.nElems = res + 1 - array->array.elems;
+ }
+}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 23203056085..f80eaeb1c6d 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -1,7 +1,7 @@
/*-------------------------------------------------------------------------
*
* jsonfuncs.c
- * Functions to process JSON data type.
+ * Functions to process JSON data types.
*
* Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -27,6 +27,7 @@
#include "utils/builtins.h"
#include "utils/hsearch.h"
#include "utils/json.h"
+#include "utils/jsonb.h"
#include "utils/jsonapi.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@@ -47,18 +48,20 @@ static void get_array_element_end(void *state, bool isnull);
static void get_scalar(void *state, char *token, JsonTokenType tokentype);
/* common worker function for json getter functions */
-static inline Datum get_path_all(PG_FUNCTION_ARGS, bool as_text);
+static inline Datum get_path_all(FunctionCallInfo fcinfo, bool as_text);
static inline text *get_worker(text *json, char *field, int elem_index,
char **tpath, int *ipath, int npath,
bool normalize_results);
+static inline Datum get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text);
/* semantic action functions for json_array_length */
static void alen_object_start(void *state);
static void alen_scalar(void *state, char *token, JsonTokenType tokentype);
static void alen_array_element_start(void *state, bool isnull);
-/* common worker for json_each* functions */
-static inline Datum each_worker(PG_FUNCTION_ARGS, bool as_text);
+/* common workers for json{b}_each* functions */
+static inline Datum each_worker(FunctionCallInfo fcinfo, bool as_text);
+static inline Datum each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text);
/* semantic action functions for json_each */
static void each_object_field_start(void *state, char *fname, bool isnull);
@@ -66,8 +69,9 @@ static void each_object_field_end(void *state, char *fname, bool isnull);
static void each_array_start(void *state);
static void each_scalar(void *state, char *token, JsonTokenType tokentype);
-/* common worker for json_each* functions */
-static inline Datum elements_worker(PG_FUNCTION_ARGS, bool as_text);
+/* common workers for json{b}_array_elements_* functions */
+static inline Datum elements_worker(FunctionCallInfo fcinfo, bool as_text);
+static inline Datum elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text);
/* semantic action functions for json_array_elements */
static void elements_object_start(void *state);
@@ -79,7 +83,7 @@ static void elements_scalar(void *state, char *token, JsonTokenType tokentype);
static HTAB *get_json_object_as_hash(text *json, char *funcname, bool use_json_as_text);
/* common worker for populate_record and to_record */
-static inline Datum populate_record_worker(PG_FUNCTION_ARGS,
+static inline Datum populate_record_worker(FunctionCallInfo fcinfo,
bool have_record_arg);
/* semantic action functions for get_json_object_as_hash */
@@ -98,8 +102,13 @@ static void populate_recordset_array_start(void *state);
static void populate_recordset_array_element_start(void *state, bool isnull);
/* worker function for populate_recordset and to_recordset */
-static inline Datum populate_recordset_worker(PG_FUNCTION_ARGS,
+static inline Datum populate_recordset_worker(FunctionCallInfo fcinfo,
bool have_record_arg);
+/* Worker that takes care of common setup for us */
+static JsonbValue *findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader,
+ uint32 flags,
+ char *key,
+ uint32 keylen);
/* search type classification for json_get* functions */
typedef enum
@@ -225,18 +234,98 @@ typedef struct PopulateRecordsetState
MemoryContext fn_mcxt; /* used to stash IO funcs */
} PopulateRecordsetState;
+/* Turn a jsonb object into a record */
+static void make_row_from_rec_and_jsonb(Jsonb * element,
+ PopulateRecordsetState *state);
+
/*
- * SQL function json_object-keys
+ * SQL function json_object_keys
*
* Returns the set of keys for the object argument.
*
* This SRF operates in value-per-call mode. It processes the
* object during the first call, and the keys are simply stashed
- * in an array, whise size is expanded as necessary. This is probably
+ * in an array, whose size is expanded as necessary. This is probably
* safe enough for a list of keys of a single object, since they are
* limited in size to NAMEDATALEN and the number of keys is unlikely to
* be so huge that it has major memory implications.
*/
+Datum
+jsonb_object_keys(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ OkeysState *state;
+ int i;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ bool skipNested = false;
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_keys on a scalar")));
+ else if (JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_keys on an array")));
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ state = palloc(sizeof(OkeysState));
+
+ state->result_size = JB_ROOT_COUNT(jb);
+ state->result_count = 0;
+ state->sent_count = 0;
+ state->result = palloc(state->result_size * sizeof(char *));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_KEY)
+ {
+ char *cstr;
+
+ cstr = palloc(v.string.len + 1 * sizeof(char));
+ memcpy(cstr, v.string.val, v.string.len);
+ cstr[v.string.len] = '\0';
+ state->result[state->result_count++] = cstr;
+ }
+ }
+
+
+ MemoryContextSwitchTo(oldcontext);
+ funcctx->user_fctx = (void *) state;
+
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ state = (OkeysState *) funcctx->user_fctx;
+
+ if (state->sent_count < state->result_count)
+ {
+ char *nxt = state->result[state->sent_count++];
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(nxt));
+ }
+
+ /* cleanup to reduce or eliminate memory leaks */
+ for (i = 0; i < state->result_count; i++)
+ pfree(state->result[i]);
+ pfree(state->result);
+ pfree(state);
+
+ SRF_RETURN_DONE(funcctx);
+}
Datum
@@ -350,9 +439,9 @@ okeys_scalar(void *state, char *token, JsonTokenType tokentype)
}
/*
- * json getter functions
+ * json and jsonb getter functions
* these implement the -> ->> #> and #>> operators
- * and the json_extract_path*(json, text, ...) functions
+ * and the json{b?}_extract_path*(json, text, ...) functions
*/
@@ -373,6 +462,51 @@ json_object_field(PG_FUNCTION_ARGS)
}
Datum
+jsonb_object_field(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ char *key = text_to_cstring(PG_GETARG_TEXT_P(1));
+ int klen = strlen(key);
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+ bool skipNested = false;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_field (jsonb -> text operator) on a scalar")));
+ else if (JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_field (jsonb -> text operator) on an array")));
+
+ Assert(JB_ROOT_IS_OBJECT(jb));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_KEY)
+ {
+ if (klen == v.string.len && strncmp(key, v.string.val, klen) == 0)
+ {
+ /*
+ * The next thing the iterator fetches should be the value, no
+ * matter what shape it is.
+ */
+ (void) JsonbIteratorNext(&it, &v, skipNested);
+ PG_RETURN_JSONB(JsonbValueToJsonb(&v));
+ }
+ }
+ }
+
+ PG_RETURN_NULL();
+}
+
+Datum
json_object_field_text(PG_FUNCTION_ARGS)
{
text *json = PG_GETARG_TEXT_P(0);
@@ -389,6 +523,74 @@ json_object_field_text(PG_FUNCTION_ARGS)
}
Datum
+jsonb_object_field_text(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ char *key = text_to_cstring(PG_GETARG_TEXT_P(1));
+ int klen = strlen(key);
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+ bool skipNested = false;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar")));
+ else if (JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_object_field_text (jsonb ->> text operator) on an array")));
+
+ Assert(JB_ROOT_IS_OBJECT(jb));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_KEY)
+ {
+ if (klen == v.string.len && strncmp(key, v.string.val, klen) == 0)
+ {
+ text *result;
+
+ /*
+ * The next thing the iterator fetches should be the value, no
+ * matter what shape it is.
+ */
+ r = JsonbIteratorNext(&it, &v, skipNested);
+
+ /*
+ * if it's a scalar string it needs to be de-escaped,
+ * otherwise just return the text
+ */
+ if (v.type == jbvString)
+ {
+ result = cstring_to_text_with_len(v.string.val, v.string.len);
+ }
+ else if (v.type == jbvNull)
+ {
+ PG_RETURN_NULL();
+ }
+ else
+ {
+ StringInfo jtext = makeStringInfo();
+ Jsonb *tjb = JsonbValueToJsonb(&v);
+
+ (void) JsonbToCString(jtext, VARDATA(tjb), -1);
+ result = cstring_to_text_with_len(jtext->data, jtext->len);
+ }
+ PG_RETURN_TEXT_P(result);
+ }
+ }
+ }
+
+ PG_RETURN_NULL();
+}
+
+Datum
json_array_element(PG_FUNCTION_ARGS)
{
text *json = PG_GETARG_TEXT_P(0);
@@ -404,6 +606,44 @@ json_array_element(PG_FUNCTION_ARGS)
}
Datum
+jsonb_array_element(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ int element = PG_GETARG_INT32(1);
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+ bool skipNested = false;
+ int element_number = 0;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_array_element (jsonb -> int operator) on a scalar")));
+ else if (JB_ROOT_IS_OBJECT(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_array_element (jsonb -> int operator) on an object")));
+
+ Assert(JB_ROOT_IS_ARRAY(jb));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_ELEM)
+ {
+ if (element_number++ == element)
+ PG_RETURN_JSONB(JsonbValueToJsonb(&v));
+ }
+ }
+
+ PG_RETURN_NULL();
+}
+
+Datum
json_array_element_text(PG_FUNCTION_ARGS)
{
text *json = PG_GETARG_TEXT_P(0);
@@ -419,6 +659,69 @@ json_array_element_text(PG_FUNCTION_ARGS)
}
Datum
+jsonb_array_element_text(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ int element = PG_GETARG_INT32(1);
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+ bool skipNested = false;
+ int element_number = 0;
+
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_array_element_text on a scalar")));
+ else if (JB_ROOT_IS_OBJECT(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_array_element_text on an object")));
+
+ Assert(JB_ROOT_IS_ARRAY(jb));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_ELEM)
+ {
+ if (element_number++ == element)
+ {
+ /*
+ * if it's a scalar string it needs to be de-escaped,
+ * otherwise just return the text
+ */
+ text *result;
+
+ if (v.type == jbvString)
+ {
+ result = cstring_to_text_with_len(v.string.val, v.string.len);
+ }
+ else if (v.type == jbvNull)
+ {
+ PG_RETURN_NULL();
+ }
+ else
+ {
+ StringInfo jtext = makeStringInfo();
+ Jsonb *tjb = JsonbValueToJsonb(&v);
+
+ (void) JsonbToCString(jtext, VARDATA(tjb), -1);
+ result = cstring_to_text_with_len(jtext->data, jtext->len);
+ }
+ PG_RETURN_TEXT_P(result);
+ }
+ }
+ }
+
+ PG_RETURN_NULL();
+}
+
+Datum
json_extract_path(PG_FUNCTION_ARGS)
{
return get_path_all(fcinfo, false);
@@ -434,9 +737,9 @@ json_extract_path_text(PG_FUNCTION_ARGS)
* common routine for extract_path functions
*/
static inline Datum
-get_path_all(PG_FUNCTION_ARGS, bool as_text)
+get_path_all(FunctionCallInfo fcinfo, bool as_text)
{
- text *json = PG_GETARG_TEXT_P(0);
+ text *json;
ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
text *result;
Datum *pathtext;
@@ -448,6 +751,8 @@ get_path_all(PG_FUNCTION_ARGS, bool as_text)
long ind;
char *endptr;
+ json = PG_GETARG_TEXT_P(0);
+
if (array_contains_nulls(path))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -486,8 +791,9 @@ get_path_all(PG_FUNCTION_ARGS, bool as_text)
result = get_worker(json, NULL, -1, tpath, ipath, npath, as_text);
if (result != NULL)
- PG_RETURN_TEXT_P(result);
+ PG_RETURN_TEXT_P(result);
else
+ /* null is NULL, regardless */
PG_RETURN_NULL();
}
@@ -668,7 +974,7 @@ get_object_field_end(void *state, char *fname, bool isnull)
/*
* make a text object from the string from the prevously noted json
* start up to the end of the previous token (the lexer is by now
- * ahead of us on whatevere came after what we're interested in).
+ * ahead of us on whatever came after what we're interested in).
*/
int len = _state->lex->prev_token_terminator - _state->result_start;
@@ -822,18 +1128,139 @@ get_scalar(void *state, char *token, JsonTokenType tokentype)
}
+Datum
+jsonb_extract_path(PG_FUNCTION_ARGS)
+{
+ return get_jsonb_path_all(fcinfo, false);
+}
+
+Datum
+jsonb_extract_path_text(PG_FUNCTION_ARGS)
+{
+ return get_jsonb_path_all(fcinfo, true);
+}
+
+static inline Datum
+get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ ArrayType *path = PG_GETARG_ARRAYTYPE_P(1);
+ Datum *pathtext;
+ bool *pathnulls;
+ int npath;
+ int i;
+ Jsonb *res;
+ bool have_object = false,
+ have_array = false;
+ JsonbValue *jbvp = NULL;
+ JsonbValue tv;
+ JsonbSuperHeader superHeader;
+
+ if (array_contains_nulls(path))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call function with null path elements")));
+
+ deconstruct_array(path, TEXTOID, -1, false, 'i',
+ &pathtext, &pathnulls, &npath);
+
+ if (JB_ROOT_IS_OBJECT(jb))
+ have_object = true;
+ else if (JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb))
+ have_array = true;
+
+ superHeader = (JsonbSuperHeader) VARDATA(jb);
+
+ for (i = 0; i < npath; i++)
+ {
+ if (have_object)
+ {
+ jbvp = findJsonbValueFromSuperHeaderLen(superHeader,
+ JB_FOBJECT,
+ VARDATA_ANY(pathtext[i]),
+ VARSIZE_ANY_EXHDR(pathtext[i]));
+ }
+ else if (have_array)
+ {
+ long lindex;
+ uint32 index;
+ char *indextext = TextDatumGetCString(pathtext[i]);
+ char *endptr;
+
+ lindex = strtol(indextext, &endptr, 10);
+ if (*endptr != '\0' || lindex > INT_MAX || lindex < 0)
+ PG_RETURN_NULL();
+ index = (uint32) lindex;
+ jbvp = getIthJsonbValueFromSuperHeader(superHeader, index);
+ }
+ else
+ {
+ if (i == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call extract path from a scalar")));
+ PG_RETURN_NULL();
+ }
+
+ if (jbvp == NULL)
+ PG_RETURN_NULL();
+ else if (i == npath - 1)
+ break;
+
+ if (jbvp->type == jbvBinary)
+ {
+ JsonbIterator *it = JsonbIteratorInit(jbvp->binary.data);
+ int r;
+
+ r = JsonbIteratorNext(&it, &tv, true);
+ superHeader = (JsonbSuperHeader) jbvp->binary.data;
+ have_object = r == WJB_BEGIN_OBJECT;
+ have_array = r == WJB_BEGIN_ARRAY;
+ }
+ else
+ {
+ have_object = jbvp->type == jbvObject;
+ have_array = jbvp->type == jbvArray;
+ }
+ }
+
+ if (as_text)
+ {
+ if (jbvp->type == jbvString)
+ PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->string.val, jbvp->string.len));
+ else if (jbvp->type == jbvNull)
+ PG_RETURN_NULL();
+ }
+
+ res = JsonbValueToJsonb(jbvp);
+
+ if (as_text)
+ {
+ PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL,
+ VARDATA(res),
+ VARSIZE(res))));
+ }
+ else
+ {
+ /* not text mode - just hand back the jsonb */
+ PG_RETURN_JSONB(res);
+ }
+}
+
/*
* SQL function json_array_length(json) -> int
*/
Datum
json_array_length(PG_FUNCTION_ARGS)
{
- text *json = PG_GETARG_TEXT_P(0);
+ text *json;
AlenState *state;
- JsonLexContext *lex = makeJsonLexContext(json, false);
+ JsonLexContext *lex;
JsonSemAction *sem;
+ json = PG_GETARG_TEXT_P(0);
+ lex = makeJsonLexContext(json, false);
state = palloc0(sizeof(AlenState));
sem = palloc0(sizeof(JsonSemAction));
@@ -853,6 +1280,23 @@ json_array_length(PG_FUNCTION_ARGS)
PG_RETURN_INT32(state->count);
}
+Datum
+jsonb_array_length(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot get array length of a scalar")));
+ else if (!JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot get array length of a non-array")));
+
+ PG_RETURN_INT32(JB_ROOT_COUNT(jb));
+}
+
/*
* These next two check ensure that the json is an array (since it can't be
* a scalar or an object).
@@ -909,22 +1353,177 @@ json_each(PG_FUNCTION_ARGS)
}
Datum
+jsonb_each(PG_FUNCTION_ARGS)
+{
+ return each_worker_jsonb(fcinfo, false);
+}
+
+Datum
json_each_text(PG_FUNCTION_ARGS)
{
return each_worker(fcinfo, true);
}
+Datum
+jsonb_each_text(PG_FUNCTION_ARGS)
+{
+ return each_worker_jsonb(fcinfo, true);
+}
+
static inline Datum
-each_worker(PG_FUNCTION_ARGS, bool as_text)
+each_worker_jsonb(FunctionCallInfo fcinfo, bool as_text)
{
- text *json = PG_GETARG_TEXT_P(0);
- JsonLexContext *lex = makeJsonLexContext(json, true);
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ ReturnSetInfo *rsi;
+ Tuplestorestate *tuple_store;
+ TupleDesc tupdesc;
+ TupleDesc ret_tdesc;
+ MemoryContext old_cxt,
+ tmp_cxt;
+ bool skipNested = false;
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+
+ if (!JB_ROOT_IS_OBJECT(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_each%s on a non-object",
+ as_text ? "_text" : "")));
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_Materialize) == 0 ||
+ rsi->expectedDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that "
+ "cannot accept a set")));
+
+
+ rsi->returnMode = SFRM_Materialize;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ ret_tdesc = CreateTupleDescCopy(tupdesc);
+ BlessTupleDesc(ret_tdesc);
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "jsonb_each temporary cxt",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_KEY)
+ {
+ text *key;
+ HeapTuple tuple;
+ Datum values[2];
+ bool nulls[2] = {false, false};
+
+ /* Use the tmp context so we can clean up after each tuple is done */
+ old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
+ key = cstring_to_text_with_len(v.string.val, v.string.len);
+
+ /*
+ * The next thing the iterator fetches should be the value, no
+ * matter what shape it is.
+ */
+ r = JsonbIteratorNext(&it, &v, skipNested);
+
+ values[0] = PointerGetDatum(key);
+
+ if (as_text)
+ {
+ if (v.type == jbvNull)
+ {
+ /* a json null is an sql null in text mode */
+ nulls[1] = true;
+ values[1] = (Datum) NULL;
+ }
+ else
+ {
+ text *sv;
+
+ if (v.type == jbvString)
+ {
+ /* In text mode, scalar strings should be dequoted */
+ sv = cstring_to_text_with_len(v.string.val, v.string.len);
+ }
+ else
+ {
+ /* Turn anything else into a json string */
+ StringInfo jtext = makeStringInfo();
+ Jsonb *jb = JsonbValueToJsonb(&v);
+
+ (void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize);
+ sv = cstring_to_text_with_len(jtext->data, jtext->len);
+ }
+
+ values[1] = PointerGetDatum(sv);
+ }
+ }
+ else
+ {
+ /* Not in text mode, just return the Jsonb */
+ Jsonb *val = JsonbValueToJsonb(&v);
+
+ values[1] = PointerGetDatum(val);
+ }
+
+ tuple = heap_form_tuple(ret_tdesc, values, nulls);
+
+ tuplestore_puttuple(tuple_store, tuple);
+
+ /* clean up and switch back */
+ MemoryContextSwitchTo(old_cxt);
+ MemoryContextReset(tmp_cxt);
+ }
+ }
+
+ MemoryContextDelete(tmp_cxt);
+
+ rsi->setResult = tuple_store;
+ rsi->setDesc = ret_tdesc;
+
+ PG_RETURN_NULL();
+}
+
+
+static inline Datum
+each_worker(FunctionCallInfo fcinfo, bool as_text)
+{
+ text *json;
+ JsonLexContext *lex;
JsonSemAction *sem;
ReturnSetInfo *rsi;
MemoryContext old_cxt;
TupleDesc tupdesc;
EachState *state;
+ json = PG_GETARG_TEXT_P(0);
+
+ lex = makeJsonLexContext(json, true);
state = palloc0(sizeof(EachState));
sem = palloc0(sizeof(JsonSemAction));
@@ -941,11 +1540,7 @@ each_worker(PG_FUNCTION_ARGS, bool as_text)
rsi->returnMode = SFRM_Materialize;
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning record called in context "
- "that cannot accept type record")));
+ (void) get_call_result_type(fcinfo, NULL, &tupdesc);
/* make these in a sufficiently long-lived memory context */
old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
@@ -1087,6 +1682,146 @@ each_scalar(void *state, char *token, JsonTokenType tokentype)
*
* a lot of this processing is similar to the json_each* functions
*/
+
+Datum
+jsonb_array_elements(PG_FUNCTION_ARGS)
+{
+ return elements_worker_jsonb(fcinfo, false);
+}
+
+Datum
+jsonb_array_elements_text(PG_FUNCTION_ARGS)
+{
+ return elements_worker_jsonb(fcinfo, true);
+}
+
+static inline Datum
+elements_worker_jsonb(FunctionCallInfo fcinfo, bool as_text)
+{
+ Jsonb *jb = PG_GETARG_JSONB(0);
+ ReturnSetInfo *rsi;
+ Tuplestorestate *tuple_store;
+ TupleDesc tupdesc;
+ TupleDesc ret_tdesc;
+ MemoryContext old_cxt,
+ tmp_cxt;
+ bool skipNested = false;
+ JsonbIterator *it;
+ JsonbValue v;
+ int r;
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot extract elements from a scalar")));
+ else if (!JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot extract elements from an object")));
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_Materialize) == 0 ||
+ rsi->expectedDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that "
+ "cannot accept a set")));
+
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* it's a simple type, so don't use get_call_result_type() */
+ tupdesc = rsi->expectedDesc;
+
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ ret_tdesc = CreateTupleDescCopy(tupdesc);
+ BlessTupleDesc(ret_tdesc);
+ tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+
+ tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "jsonb_each temporary cxt",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_ELEM)
+ {
+ HeapTuple tuple;
+ Datum values[1];
+ bool nulls[1] = {false};
+
+ /* use the tmp context so we can clean up after each tuple is done */
+ old_cxt = MemoryContextSwitchTo(tmp_cxt);
+
+ if (!as_text)
+ {
+ Jsonb *val = JsonbValueToJsonb(&v);
+
+ values[0] = PointerGetDatum(val);
+ }
+ else
+ {
+ if (v.type == jbvNull)
+ {
+ /* a json null is an sql null in text mode */
+ nulls[0] = true;
+ values[0] = (Datum) NULL;
+ }
+ else
+ {
+ text *sv;
+
+ if (v.type == jbvString)
+ {
+ /* in text mode scalar strings should be dequoted */
+ sv = cstring_to_text_with_len(v.string.val, v.string.len);
+ }
+ else
+ {
+ /* turn anything else into a json string */
+ StringInfo jtext = makeStringInfo();
+ Jsonb *jb = JsonbValueToJsonb(&v);
+
+ (void) JsonbToCString(jtext, VARDATA(jb), 2 * v.estSize);
+ sv = cstring_to_text_with_len(jtext->data, jtext->len);
+ }
+
+ values[0] = PointerGetDatum(sv);
+ }
+ }
+
+ tuple = heap_form_tuple(ret_tdesc, values, nulls);
+
+ tuplestore_puttuple(tuple_store, tuple);
+
+ /* clean up and switch back */
+ MemoryContextSwitchTo(old_cxt);
+ MemoryContextReset(tmp_cxt);
+ }
+ }
+
+ MemoryContextDelete(tmp_cxt);
+
+ rsi->setResult = tuple_store;
+ rsi->setDesc = ret_tdesc;
+
+ PG_RETURN_NULL();
+}
+
Datum
json_array_elements(PG_FUNCTION_ARGS)
{
@@ -1100,7 +1835,7 @@ json_array_elements_text(PG_FUNCTION_ARGS)
}
static inline Datum
-elements_worker(PG_FUNCTION_ARGS, bool as_text)
+elements_worker(FunctionCallInfo fcinfo, bool as_text)
{
text *json = PG_GETARG_TEXT_P(0);
@@ -1270,9 +2005,16 @@ elements_scalar(void *state, char *token, JsonTokenType tokentype)
* which is in turn partly adapted from record_out.
*
* The json is decomposed into a hash table, in which each
- * field in the record is then looked up by name.
+ * field in the record is then looked up by name. For jsonb
+ * we fetch the values direct from the object.
*/
Datum
+jsonb_populate_record(PG_FUNCTION_ARGS)
+{
+ return populate_record_worker(fcinfo, true);
+}
+
+Datum
json_populate_record(PG_FUNCTION_ARGS)
{
return populate_record_worker(fcinfo, true);
@@ -1285,11 +2027,14 @@ json_to_record(PG_FUNCTION_ARGS)
}
static inline Datum
-populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
+populate_record_worker(FunctionCallInfo fcinfo, bool have_record_arg)
{
+ Oid argtype;
+ Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, have_record_arg ? 1 : 0);
text *json;
+ Jsonb *jb = NULL;
bool use_json_as_text;
- HTAB *json_hash;
+ HTAB *json_hash = NULL;
HeapTupleHeader rec = NULL;
Oid tupType = InvalidOid;
int32 tupTypmod = -1;
@@ -1301,19 +2046,20 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
int i;
Datum *values;
bool *nulls;
- char fname[NAMEDATALEN];
- JsonHashEntry *hashentry;
+
+ Assert(jtype == JSONOID || jtype == JSONBOID);
+
+ use_json_as_text = PG_ARGISNULL(have_record_arg ? 2 : 1) ? false :
+ PG_GETARG_BOOL(have_record_arg ? 2 : 1);
if (have_record_arg)
{
- Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
-
- use_json_as_text = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2);
+ argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
if (!type_is_rowtype(argtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of json_populate_record must be a row type")));
+ errmsg("first argument of json%s_populate_record must be a row type", jtype == JSONBOID ? "b" : "")));
if (PG_ARGISNULL(0))
{
@@ -1340,19 +2086,16 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
tupTypmod = HeapTupleHeaderGetTypMod(rec);
}
- json = PG_GETARG_TEXT_P(1);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
}
else
- {
- /* json_to_record case */
+ { /* json{b}_to_record case */
use_json_as_text = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
- json = PG_GETARG_TEXT_P(0);
-
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -1362,11 +2105,13 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
"using a column definition list.")));
}
- json_hash = get_json_object_as_hash(json, "json_populate_record",
- use_json_as_text);
-
- if (have_record_arg)
+ if (jtype == JSONOID)
{
+ /* just get the text */
+ json = PG_GETARG_TEXT_P(have_record_arg ? 1 : 0);
+
+ json_hash = get_json_object_as_hash(json, "json_populate_record", use_json_as_text);
+
/*
* if the input json is empty, we can only skip the rest if we were
* passed in a non-null record, since otherwise there may be issues
@@ -1375,8 +2120,14 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
if (hash_get_num_entries(json_hash) == 0 && rec)
PG_RETURN_POINTER(rec);
+ }
+ else
+ {
+ jb = PG_GETARG_JSONB(have_record_arg ? 1 : 0);
- tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ /* same logic as for json */
+ if (!have_record_arg && rec)
+ PG_RETURN_POINTER(rec);
}
ncolumns = tupdesc->natts;
@@ -1439,7 +2190,9 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
{
ColumnIOData *column_info = &my_extra->columns[i];
Oid column_type = tupdesc->attrs[i]->atttypid;
- char *value;
+ JsonbValue *v = NULL;
+ char fname[NAMEDATALEN];
+ JsonHashEntry *hashentry = NULL;
/* Ignore dropped columns in datatype */
if (tupdesc->attrs[i]->attisdropped)
@@ -1448,9 +2201,20 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
continue;
}
- memset(fname, 0, NAMEDATALEN);
- strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
- hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ if (jtype == JSONOID)
+ {
+
+ memset(fname, 0, NAMEDATALEN);
+ strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
+ hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
+ }
+ else
+ {
+ char *key = NameStr(tupdesc->attrs[i]->attname);
+
+ v = findJsonbValueFromSuperHeaderLen(VARDATA(jb), JB_FOBJECT, key,
+ strlen(key));
+ }
/*
* we can't just skip here if the key wasn't found since we might have
@@ -1460,7 +2224,8 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
* then every field which we don't populate needs to be run through
* the input function just in case it's a domain type.
*/
- if (hashentry == NULL && rec)
+ if (((jtype == JSONOID && hashentry == NULL) ||
+ (jtype == JSONBOID && v == NULL)) && rec)
continue;
/*
@@ -1475,7 +2240,8 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
fcinfo->flinfo->fn_mcxt);
column_info->column_type = column_type;
}
- if (hashentry == NULL || hashentry->isnull)
+ if ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) ||
+ (jtype == JSONBOID && (v == NULL || v->type == jbvNull)))
{
/*
* need InputFunctionCall to happen even for nulls, so that domain
@@ -1488,9 +2254,33 @@ populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg)
}
else
{
- value = hashentry->val;
+ char *s = NULL;
- values[i] = InputFunctionCall(&column_info->proc, value,
+ if (jtype == JSONOID)
+ {
+ /* already done the hard work in the json case */
+ s = hashentry->val;
+ }
+ else
+ {
+ if (v->type == jbvString)
+ s = pnstrdup(v->string.val, v->string.len);
+ else if (v->type == jbvBool)
+ s = pnstrdup((v->boolean) ? "t" : "f", 1);
+ else if (v->type == jbvNumeric)
+ s = DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v->numeric)));
+ else if (!use_json_as_text)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot populate with a nested object unless use_json_as_text is true")));
+ else if (v->type == jbvBinary)
+ s = JsonbToCString(NULL, v->binary.data, v->binary.len);
+ else
+ elog(ERROR, "invalid jsonb type");
+ }
+
+ values[i] = InputFunctionCall(&column_info->proc, s,
column_info->typioparam,
tupdesc->attrs[i]->atttypmod);
nulls[i] = false;
@@ -1656,6 +2446,134 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype)
* per object in the array.
*/
Datum
+jsonb_populate_recordset(PG_FUNCTION_ARGS)
+{
+ return populate_recordset_worker(fcinfo, true);
+}
+
+static void
+make_row_from_rec_and_jsonb(Jsonb * element, PopulateRecordsetState *state)
+{
+ Datum *values;
+ bool *nulls;
+ int i;
+ RecordIOData *my_extra = state->my_extra;
+ int ncolumns = my_extra->ncolumns;
+ TupleDesc tupdesc = state->ret_tdesc;
+ HeapTupleHeader rec = state->rec;
+ HeapTuple rettuple;
+
+ values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+ if (state->rec)
+ {
+ HeapTupleData tuple;
+
+ /* Build a temporary HeapTuple control structure */
+ tuple.t_len = HeapTupleHeaderGetDatumLength(state->rec);
+ ItemPointerSetInvalid(&(tuple.t_self));
+ tuple.t_tableOid = InvalidOid;
+ tuple.t_data = state->rec;
+
+ /* Break down the tuple into fields */
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ }
+ else
+ {
+ for (i = 0; i < ncolumns; ++i)
+ {
+ values[i] = (Datum) 0;
+ nulls[i] = true;
+ }
+ }
+
+ for (i = 0; i < ncolumns; ++i)
+ {
+ ColumnIOData *column_info = &my_extra->columns[i];
+ Oid column_type = tupdesc->attrs[i]->atttypid;
+ JsonbValue *v = NULL;
+ char *key;
+
+ /* Ignore dropped columns in datatype */
+ if (tupdesc->attrs[i]->attisdropped)
+ {
+ nulls[i] = true;
+ continue;
+ }
+
+ key = NameStr(tupdesc->attrs[i]->attname);
+
+ v = findJsonbValueFromSuperHeaderLen(VARDATA(element), JB_FOBJECT,
+ key, strlen(key));
+
+ /*
+ * We can't just skip here if the key wasn't found since we might have
+ * a domain to deal with. If we were passed in a non-null record
+ * datum, we assume that the existing values are valid (if they're
+ * not, then it's not our fault), but if we were passed in a null,
+ * then every field which we don't populate needs to be run through
+ * the input function just in case it's a domain type.
+ */
+ if (v == NULL && rec)
+ continue;
+
+ /*
+ * Prepare to convert the column value from text
+ */
+ if (column_info->column_type != column_type)
+ {
+ getTypeInputInfo(column_type,
+ &column_info->typiofunc,
+ &column_info->typioparam);
+ fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
+ state->fn_mcxt);
+ column_info->column_type = column_type;
+ }
+ if (v == NULL || v->type == jbvNull)
+ {
+ /*
+ * Need InputFunctionCall to happen even for nulls, so that domain
+ * checks are done
+ */
+ values[i] = InputFunctionCall(&column_info->proc, NULL,
+ column_info->typioparam,
+ tupdesc->attrs[i]->atttypmod);
+ nulls[i] = true;
+ }
+ else
+ {
+ char *s = NULL;
+
+ if (v->type == jbvString)
+ s = pnstrdup(v->string.val, v->string.len);
+ else if (v->type == jbvBool)
+ s = pnstrdup((v->boolean) ? "t" : "f", 1);
+ else if (v->type == jbvNumeric)
+ s = DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(v->numeric)));
+ else if (!state->use_json_as_text)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot populate with a nested object unless use_json_as_text is true")));
+ else if (v->type == jbvBinary)
+ s = JsonbToCString(NULL, v->binary.data, v->binary.len);
+ else
+ elog(ERROR, "invalid jsonb type");
+
+ values[i] = InputFunctionCall(&column_info->proc, s,
+ column_info->typioparam,
+ tupdesc->attrs[i]->atttypmod);
+ nulls[i] = false;
+ }
+ }
+
+ rettuple = heap_form_tuple(tupdesc, values, nulls);
+
+ tuplestore_puttuple(state->tuple_store, rettuple);
+}
+
+Datum
json_populate_recordset(PG_FUNCTION_ARGS)
{
return populate_recordset_worker(fcinfo, true);
@@ -1671,10 +2589,10 @@ json_to_recordset(PG_FUNCTION_ARGS)
* common worker for json_populate_recordset() and json_to_recordset()
*/
static inline Datum
-populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
+populate_recordset_worker(FunctionCallInfo fcinfo, bool have_record_arg)
{
Oid argtype;
- text *json;
+ Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, have_record_arg ? 1 : 0);
bool use_json_as_text;
ReturnSetInfo *rsi;
MemoryContext old_cxt;
@@ -1684,8 +2602,6 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
TupleDesc tupdesc;
RecordIOData *my_extra;
int ncolumns;
- JsonLexContext *lex;
- JsonSemAction *sem;
PopulateRecordsetState *state;
if (have_record_arg)
@@ -1721,7 +2637,8 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
/*
* get the tupdesc from the result set info - it must be a record type
- * because we already checked that arg1 is a record type.
+ * because we already checked that arg1 is a record type, or we're in a
+ * to_record function which returns a setof record.
*/
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
ereport(ERROR,
@@ -1729,29 +2646,12 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
errmsg("function returning record called in context "
"that cannot accept type record")));
- state = palloc0(sizeof(PopulateRecordsetState));
- sem = palloc0(sizeof(JsonSemAction));
-
-
- /* make these in a sufficiently long-lived memory context */
- old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
-
- state->ret_tdesc = CreateTupleDescCopy(tupdesc);
- BlessTupleDesc(state->ret_tdesc);
- state->tuple_store =
- tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
- false, work_mem);
-
- MemoryContextSwitchTo(old_cxt);
-
/* if the json is null send back an empty set */
if (have_record_arg)
{
if (PG_ARGISNULL(1))
PG_RETURN_NULL();
- json = PG_GETARG_TEXT_P(1);
-
if (PG_ARGISNULL(0))
rec = NULL;
else
@@ -1759,11 +2659,9 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
}
else
{
- if (PG_ARGISNULL(0))
+ if (PG_ARGISNULL(1))
PG_RETURN_NULL();
- json = PG_GETARG_TEXT_P(0);
-
rec = NULL;
}
@@ -1771,8 +2669,6 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
tupTypmod = tupdesc->tdtypmod;
ncolumns = tupdesc->natts;
- lex = makeJsonLexContext(json, true);
-
/*
* We arrange to look up the needed I/O info just once per series of
* calls, assuming the record type doesn't change underneath us.
@@ -1801,23 +2697,80 @@ populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg)
my_extra->ncolumns = ncolumns;
}
- sem->semstate = (void *) state;
- sem->array_start = populate_recordset_array_start;
- sem->array_element_start = populate_recordset_array_element_start;
- sem->scalar = populate_recordset_scalar;
- sem->object_field_start = populate_recordset_object_field_start;
- sem->object_field_end = populate_recordset_object_field_end;
- sem->object_start = populate_recordset_object_start;
- sem->object_end = populate_recordset_object_end;
+ state = palloc0(sizeof(PopulateRecordsetState));
- state->lex = lex;
+ /* make these in a sufficiently long-lived memory context */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+ state->ret_tdesc = CreateTupleDescCopy(tupdesc);;
+ BlessTupleDesc(state->ret_tdesc);
+ state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
+ SFRM_Materialize_Random,
+ false, work_mem);
+ MemoryContextSwitchTo(old_cxt);
state->my_extra = my_extra;
state->rec = rec;
state->use_json_as_text = use_json_as_text;
state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
- pg_parse_json(lex, sem);
+ if (jtype == JSONOID)
+ {
+ text *json = PG_GETARG_TEXT_P(have_record_arg ? 1 : 0);
+ JsonLexContext *lex;
+ JsonSemAction *sem;
+
+ sem = palloc0(sizeof(JsonSemAction));
+
+ lex = makeJsonLexContext(json, true);
+
+ sem->semstate = (void *) state;
+ sem->array_start = populate_recordset_array_start;
+ sem->array_element_start = populate_recordset_array_element_start;
+ sem->scalar = populate_recordset_scalar;
+ sem->object_field_start = populate_recordset_object_field_start;
+ sem->object_field_end = populate_recordset_object_field_end;
+ sem->object_start = populate_recordset_object_start;
+ sem->object_end = populate_recordset_object_end;
+
+ state->lex = lex;
+
+ pg_parse_json(lex, sem);
+
+ }
+ else
+ {
+ Jsonb *jb;
+ JsonbIterator *it;
+ JsonbValue v;
+ bool skipNested = false;
+ int r;
+
+ Assert(jtype == JSONBOID);
+ jb = PG_GETARG_JSONB(have_record_arg ? 1 : 0);
+
+ if (JB_ROOT_IS_SCALAR(jb) || !JB_ROOT_IS_ARRAY(jb))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot call jsonb_populate_recordset on non-array")));
+
+ it = JsonbIteratorInit(VARDATA_ANY(jb));
+
+ while ((r = JsonbIteratorNext(&it, &v, skipNested)) != WJB_DONE)
+ {
+ skipNested = true;
+
+ if (r == WJB_ELEM)
+ {
+ Jsonb *element = JsonbValueToJsonb(&v);
+
+ if (!JB_ROOT_IS_OBJECT(element))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("jsonb_populate_recordset argument must be an array of objects")));
+ make_row_from_rec_and_jsonb(element, state);
+ }
+ }
+ }
rsi->setResult = state->tuple_store;
rsi->setDesc = state->ret_tdesc;
@@ -2067,3 +3020,19 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull)
hashentry->val = _state->saved_scalar;
}
}
+
+/*
+ * findJsonbValueFromSuperHeader() wrapper that sets up JsonbValue key string.
+ */
+static JsonbValue *
+findJsonbValueFromSuperHeaderLen(JsonbSuperHeader sheader, uint32 flags,
+ char *key, uint32 keylen)
+{
+ JsonbValue k;
+
+ k.type = jbvString;
+ k.string.val = key;
+ k.string.len = keylen;
+
+ return findJsonbValueFromSuperHeader(sheader, flags, NULL, &k);
+}
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index b78451dda01..64eb0f8d16e 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -627,6 +627,44 @@ numeric_out_sci(Numeric num, int scale)
}
/*
+ * numeric_normalize() -
+ *
+ * Output function for numeric data type without trailing zeroes.
+ */
+char *
+numeric_normalize(Numeric num)
+{
+ NumericVar x;
+ char *str;
+ int orig, last;
+
+ /*
+ * Handle NaN
+ */
+ if (NUMERIC_IS_NAN(num))
+ return pstrdup("NaN");
+
+ init_var_from_num(num, &x);
+
+ str = get_str_from_var(&x);
+
+ orig = last = strlen(str) - 1;
+
+ for (;;)
+ {
+ if (last == 0 || str[last] != '0')
+ break;
+
+ last--;
+ }
+
+ if (last > 0 && last != orig)
+ str[last] = '\0';
+
+ return str;
+}
+
+/*
* numeric_recv - converts external binary format to numeric
*
* External format is a sequence of int16's: