diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 43b7e5f9e7..2b60fbed0e 100644 --- a/contrib/hstore/Makefile +++ b/contrib/hstore/Makefile @@ -5,7 +5,8 @@ OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ crc32.o EXTENSION = hstore -DATA = hstore--1.2.sql hstore--1.1--1.2.sql hstore--1.0--1.1.sql \ +DATA = hstore--1.3.sql hstore--1.2--1.3.sql \ + hstore--1.1--1.2.sql hstore--1.0--1.1.sql \ hstore--unpackaged--1.0.sql REGRESS = hstore diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out index 2114143022..9749e45c14 100644 --- a/contrib/hstore/expected/hstore.out +++ b/contrib/hstore/expected/hstore.out @@ -1453,7 +1453,7 @@ select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexe 1 (1 row) --- json +-- json and jsonb select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); hstore_to_json ------------------------------------------------------------------------------------------------- @@ -1472,6 +1472,24 @@ select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012 {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1} (1 row) +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); + jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_jsonb_loose +--------------------------------------------------------------------------------------- + {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "a key": 1} +(1 row) + create table test_json_agg (f1 text, f2 hstore); insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql new file mode 100644 index 0000000000..0a7056015b --- /dev/null +++ b/contrib/hstore/hstore--1.2--1.3.sql @@ -0,0 +1,17 @@ +/* contrib/hstore/hstore--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.3'" to load this file. \quit + +CREATE FUNCTION hstore_to_jsonb(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb' +LANGUAGE C IMMUTABLE STRICT; + +CREATE CAST (hstore AS jsonb) + WITH FUNCTION hstore_to_jsonb(hstore); + +CREATE FUNCTION hstore_to_jsonb_loose(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose' +LANGUAGE C IMMUTABLE STRICT; diff --git a/contrib/hstore/hstore--1.2.sql b/contrib/hstore/hstore--1.3.sql similarity index 97% rename from contrib/hstore/hstore--1.2.sql rename to contrib/hstore/hstore--1.3.sql index f415a7230b..995ade1b3c 100644 --- a/contrib/hstore/hstore--1.2.sql +++ b/contrib/hstore/hstore--1.3.sql @@ -1,4 +1,4 @@ -/* contrib/hstore/hstore--1.1.sql */ +/* contrib/hstore/hstore--1.3.sql */ -- complain if script is sourced in psql, rather than via CREATE EXTENSION \echo Use "CREATE EXTENSION hstore" to load this file. \quit @@ -247,6 +247,19 @@ RETURNS json AS 'MODULE_PATHNAME', 'hstore_to_json_loose' LANGUAGE C IMMUTABLE STRICT; +CREATE FUNCTION hstore_to_jsonb(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb' +LANGUAGE C IMMUTABLE STRICT; + +CREATE CAST (hstore AS jsonb) + WITH FUNCTION hstore_to_jsonb(hstore); + +CREATE FUNCTION hstore_to_jsonb_loose(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose' +LANGUAGE C IMMUTABLE STRICT; + CREATE FUNCTION hstore(record) RETURNS hstore AS 'MODULE_PATHNAME', 'hstore_from_record' diff --git a/contrib/hstore/hstore.control b/contrib/hstore/hstore.control index 9daf5e2e00..dcc3b687cf 100644 --- a/contrib/hstore/hstore.control +++ b/contrib/hstore/hstore.control @@ -1,5 +1,5 @@ # hstore extension comment = 'data type for storing sets of (key, value) pairs' -default_version = '1.2' +default_version = '1.3' module_pathname = '$libdir/hstore' relocatable = true diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 65e4438d32..6cfc935788 100644 --- a/contrib/hstore/hstore_io.c +++ b/contrib/hstore/hstore_io.c @@ -12,6 +12,7 @@ #include "libpq/pqformat.h" #include "utils/builtins.h" #include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/typcache.h" @@ -1374,3 +1375,167 @@ hstore_to_json(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(dst.data)); } + +PG_FUNCTION_INFO_V1(hstore_to_jsonb); +Datum hstore_to_jsonb(PG_FUNCTION_ARGS); +Datum +hstore_to_jsonb(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HS(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + + res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, val; + + key.estSize = sizeof(JEntry); + key.type = jbvString; + key.string.len = HS_KEYLEN(entries, i); + key.string.val = pnstrdup(HS_KEY(entries, base, i), key.string.len); + key.estSize += key.string.len; + + res = pushJsonbValue(&state, WJB_KEY, &key); + + if (HS_VALISNULL(entries, i)) + { + val.estSize = sizeof(JEntry); + val.type = jbvNull; + } + else + { + val.estSize = sizeof(JEntry); + val.type = jbvString; + val.string.len = HS_VALLEN(entries, i); + val.string.val = pnstrdup(HS_VAL(entries, base, i), val.string.len); + val.estSize += val.string.len; + } + res = pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} + +PG_FUNCTION_INFO_V1(hstore_to_jsonb_loose); +Datum hstore_to_jsonb_loose(PG_FUNCTION_ARGS); +Datum +hstore_to_jsonb_loose(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HS(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + StringInfoData tmp; + bool is_number; + + initStringInfo(&tmp); + + res = pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, val; + + key.estSize = sizeof(JEntry); + key.type = jbvString; + key.string.len = HS_KEYLEN(entries, i); + key.string.val = pnstrdup(HS_KEY(entries, base, i), key.string.len); + key.estSize += key.string.len; + + res = pushJsonbValue(&state, WJB_KEY, &key); + + val.estSize = sizeof(JEntry); + + if (HS_VALISNULL(entries, i)) + { + val.type = jbvNull; + } + /* guess that values of 't' or 'f' are booleans */ + else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 't') + { + val.type = jbvBool; + val.boolean = true; + } + else if (HS_VALLEN(entries, i) == 1 && *(HS_VAL(entries, base, i)) == 'f') + { + val.type = jbvBool; + val.boolean = false; + } + else + { + is_number = false; + resetStringInfo(&tmp); + + appendBinaryStringInfo(&tmp, HS_VAL(entries, base, i), HS_VALLEN(entries, i)); + + /* + * don't treat something with a leading zero followed by another + * digit as numeric - could be a zip code or similar + */ + if (tmp.len > 0 && + !(tmp.data[0] == '0' && + isdigit((unsigned char) tmp.data[1])) && + strspn(tmp.data, "+-0123456789Ee.") == tmp.len) + { + /* + * might be a number. See if we can input it as a numeric + * value. Ignore any actual parsed value. + */ + char *endptr = "junk"; + long lval; + + lval = strtol(tmp.data, &endptr, 10); + (void) lval; + if (*endptr == '\0') + { + /* + * strol man page says this means the whole string is + * valid + */ + is_number = true; + } + else + { + /* not an int - try a double */ + double dval; + + dval = strtod(tmp.data, &endptr); + (void) dval; + if (*endptr == '\0') + is_number = true; + } + } + if (is_number) + { + val.type = jbvNumeric; + val.numeric = DatumGetNumeric( + DirectFunctionCall3(numeric_in, CStringGetDatum(tmp.data), 0, -1)); + val.estSize += VARSIZE_ANY(val.numeric) +sizeof(JEntry); + } + else + { + val.estSize = sizeof(JEntry); + val.type = jbvString; + val.string.len = HS_VALLEN(entries, i); + val.string.val = pnstrdup(HS_VAL(entries, base, i), val.string.len); + val.estSize += val.string.len; + } + } + res = pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql index 9518f569ec..5a9e9ee5ae 100644 --- a/contrib/hstore/sql/hstore.sql +++ b/contrib/hstore/sql/hstore.sql @@ -331,11 +331,15 @@ set enable_seqscan=off; select count(*) from testhstore where h #># 'p=>1'; select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; --- json +-- json and jsonb select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json); select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + create table test_json_agg (f1 text, f2 hstore); insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index ac285ce011..cc458b4753 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -139,7 +139,13 @@ json - JSON data + textual JSON data + + + + jsonb + + binary JSON data, decomposed @@ -4220,34 +4226,7 @@ SET xmloption TO { DOCUMENT | CONTENT }; - - <acronym>JSON</> Type - - - JSON - - - - The json data type can be used to store JSON (JavaScript - Object Notation) data, as specified in RFC 4627. Such - data can also be stored as text, but the - json data type has the advantage of checking that each - stored value is a valid JSON value. There are also related support - functions available; see . - - - - PostgreSQL allows only one server encoding - per database. It is therefore not possible for JSON to conform rigidly - to the specification unless the server encoding is UTF-8. Attempts to - directly include characters which cannot be represented in the server - encoding will fail; conversely, characters which can be represented in - the server encoding but not in UTF-8 will be allowed. - \uXXXX escapes are allowed regardless of the server - encoding, and are checked only for syntactic correctness. - - + &json; &array; diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 6c8e254a58..ab6fcf7838 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -22,6 +22,7 @@ + diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 71b9829d85..4e2fff7cd7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10079,12 +10079,13 @@ table2-mapping - shows the operators that are - available for use with JSON (see ) data. + shows the operators that + are available for use with the two JSON datatypes (see ). - JSON Operators + <type>json</> and <type>jsonb</> Operators @@ -10121,13 +10122,13 @@ table2-mapping #> - array of text + text[] Get JSON object at specified path '{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}' #>> - array of text + text[] Get JSON object at specified path as text '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' @@ -10135,13 +10136,107 @@ table2-mapping
+ + + There are parallel variants of these operators for both the + json and jsonb types. In addition to + those operators common to both types, a further set of operators + exists for jsonb (which comprise the default + GIN operator class). + + - shows the functions that are available - for creating and manipulating JSON (see ) data. + The following are jsonb-only operators, used by + jsonb operator classes. For a full description of + jsonb containment semantics and nesting, see . + describes how these operators can be used to effectively index + jsonb. + + + Additonal JSONB Operators + + + + Operator + Right Operand Type + Description + Example + + + + + = + jsonb + Is the jsonb equal to this jsonb? + '[1,2,3]'::jsonb = '[1,2,3]'::jsonb + + + @> + jsonb + Does the jsonb contain within it this jsonb? + '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb + + + <@ + jsonb + Does the jsonb have contained within it this jsonb? + '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb + + + ? + text + Does this key/element string exist? + '{"a":1, "b":2}'::jsonb ? 'b' + + + ?| + text[] + Do any of these key/element strings exist? + '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] + + + ?& + text[] + Do all of these key/element strings exist? + '["a", "b"]'::jsonb ?& array['a', 'b'] + + + +
+ + + + shows the functions that are + available for creating json values. + (see ) - - JSON Support Functions + + array_to_json + + + row_to_json + + + to_json + + + json_build_array + + + json_build_object + + + json_object + + +
+ JSON Creation Functions @@ -10155,9 +10250,6 @@ table2-mapping - - array_to_json - array_to_json(anyarray [, pretty_bool]) json @@ -10171,9 +10263,6 @@ table2-mapping - - row_to_json - row_to_json(record [, pretty_bool]) json @@ -10186,9 +10275,6 @@ table2-mapping - - to_json - to_json(anyelement) json @@ -10204,223 +10290,6 @@ table2-mapping - - json_array_length - - json_array_length(json) - - int - - Returns the number of elements in the outermost JSON array. - - json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') - 5 - - - - - json_each - - json_each(json) - - SETOF key text, value json - - Expands the outermost JSON object into a set of key/value pairs. - - select * from json_each('{"a":"foo", "b":"bar"}') - - - key | value ------+------- - a | "foo" - b | "bar" - - - - - - - json_each_text - - json_each_text(from_json json) - - SETOF key text, value text - - Expands the outermost JSON object into a set of key/value pairs. The - returned value will be of type text. - - select * from json_each_text('{"a":"foo", "b":"bar"}') - - - key | value ------+------- - a | foo - b | bar - - - - - - - json_extract_path - - json_extract_path(from_json json, VARIADIC path_elems text[]) - - json - - Returns JSON value pointed to by path_elems. - - json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4') - {"f5":99,"f6":"foo"} - - - - - json_extract_path_text - - json_extract_path_text(from_json json, VARIADIC path_elems text[]) - - text - - Returns JSON value pointed to by path_elems. - - json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6') - foo - - - - - json_object_keys - - json_object_keys(json) - - SETOF text - - Returns set of keys in the JSON object. Only the outer object will be displayed. - - json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') - - - json_object_keys ------------------- - f1 - f2 - - - - - - - json_populate_record - - json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false] - - anyelement - - Expands the object in from_json to a row whose columns match - the record type defined by base. Conversion will be best - effort; columns in base with no corresponding key in from_json - will be left null. If a column is specified more than once, the last value is used. - - select * from json_populate_record(null::x, '{"a":1,"b":2}') - - - a | b ----+--- - 1 | 2 - - - - - - - json_populate_recordset - - json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false] - - SETOF anyelement - - Expands the outermost set of objects in from_json to a set - whose columns match the record type defined by base. - Conversion will be best effort; columns in base with no - corresponding key in from_json will be left null. - If a column is specified more than once, the last value is used. - - select * from json_populate_recordset(null::x, '[{"a":1,"b":2},{"a":3,"b":4}]') - - - a | b ----+--- - 1 | 2 - 3 | 4 - - - - - - - json_array_elements - - json_array_elements(json) - - SETOF json - - Expands a JSON array to a set of JSON values. - - SELECT * FROM json_array_elements('[1,true, [2,false]]') - - - value ------------ - 1 - true - [2,false] - - - - - - - json_array_elements_text - - json_array_elements_text(json) - - SETOF text - - Expands a JSON array to a set of text values. - - SELECT * FROM json_array_elements_text('["foo", "bar"]') - - - value ------------ - foo - bar - - - - - - - json_typeof - - json_typeof(json) - - text - - Returns the type of the outermost JSON value as a text string. The types are - object, array, string, number, - boolean, and null. (See note below regarding the - distinction between a JSON null and a SQL NULL.) - - json_typeof('-123.4') - number - - - - - json_build_array - json_build_array(VARIADIC "any") json @@ -10438,16 +10307,13 @@ table2-mapping - - json_build_object - json_build_object(VARIADIC "any") json - Builds a JSON array out of a variadic argument list. - By convention, the object is - constructed out of alternating name/value arguments. + Builds a JSON array out of a variadic argument list. By + convention, the object is constructed out of alternating + name/value arguments. SELECT json_build_object('foo',1,'bar',2); @@ -10460,9 +10326,6 @@ table2-mapping - - json_object - json_object(text[]) json @@ -10473,7 +10336,7 @@ table2-mapping such that each inner array has exactly two elements, which are taken as a name/value pair. - select * from json_object('{a, 1, b, "def", c, 3.5}') or select * from json_object('{{a, 1},{b, "def"},{c, 3.5}}') + select * from json_object('{a, 1, b, "def", c, 3.5}') or select json_object('{{a, 1},{b, "def"},{c, 3.5}}') json_object @@ -10491,7 +10354,7 @@ table2-mapping The two-argument form of JSON object takes keys and values pairwise from two separate arrays. In all other respects it is identical to the one-argument form. - select * from json_object('{a, b}', '{1,2}'); + select json_object('{a, b}', '{1,2}'); json_object @@ -10500,16 +10363,298 @@ table2-mapping + + +
+ + + + shows the functions that + are available for processing json and jsonb values. + (see ) + + + + json_array_length + + + jsonb_array_length + + + json_each + + + jsonb_each + + + json_each_text + + + jsonb_each_text + + + json_extract_path + + + jsonb_extract_path + + + json_extract_path_text + + + jsonb_extract_path_text + + + json_object_keys + + + jsonb_object_keys + + + json_populate_record + + + jsonb_populate_record + + + json_populate_recordset + + + jsonb_populate_recordset + + + json_array_elements + + + jsonb_array_elements + + + json_array_elements_text + + + jsonb_array_elements_text + + + json_typeof + + + jsonb_typeof + + + json_to_record + + + json_to_recordset + + + + JSON Processing Functions + + + + Function + Return Type + Description + Example + Example Result + + + + + json_array_length(json) + jsonb_array_length(jsonb) + + int + + Returns the number of elements in the outermost JSON array. + + json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') + 5 + + + json_each(json) + jsonb_each(jsonb) + + SETOF key text, value json + SETOF key text, value jsonb + + + Expands the outermost JSON object into a set of key/value pairs. + + select * from json_each('{"a":"foo", "b":"bar"}') + + + key | value +-----+------- + a | "foo" + b | "bar" + + + + + json_each_text(from_json json) + jsonb_each_text(from_json jsonb) + + SETOF key text, value text + + Expands the outermost JSON object into a set of key/value pairs. The + returned value will be of type text. + + select * from json_each_text('{"a":"foo", "b":"bar"}') + + + key | value +-----+------- + a | foo + b | bar + + + + + json_extract_path(from_json json, VARIADIC path_elems text[]) + jsonb_extract_path(from_jsonb jsonb, VARIADIC path_elems text[]) + + jsonjsonb + + + Returns JSON value pointed to by path_elems. + + json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4') + {"f5":99,"f6":"foo"} + + + json_extract_path_text(from_json json, VARIADIC path_elems text[]) + json_extract_path_text(from_json json, VARIADIC path_elems text[]) + + text + + Returns JSON value pointed to by path_elems. + + json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6') + foo + + + json_object_keys(json) + jsonb_object_keys(jsonb) + + SETOF text + + Returns set of keys in the JSON object. Only the outer object will be displayed. + + json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') + + + json_object_keys +------------------ + f1 + f2 + + + + + json_populate_record(base anyelement, from_json json, [, use_json_as_text bool=false]) + jsonb_populate_record(base anyelement, from_json jsonb, [, use_json_as_text bool=false]) + + anyelement + + Expands the object in from_json to a row whose columns match + the record type defined by base. Conversion will be best + effort; columns in base with no corresponding key in from_json + will be left null. When processing json, if a + column is specified more than once, the last value is used. + + select * from json_populate_record(null::x, '{"a":1,"b":2}') + + + a | b +---+--- + 1 | 2 + + + + + json_populate_recordset(base anyelement, from_json json, [, use_json_as_text bool=false]) + jsonb_populate_recordset(base anyelement, from_json jsonb, [, use_json_as_text bool=false]) + + SETOF anyelement + + Expands the outermost set of objects in from_json to a set + whose columns match the record type defined by base. + Conversion will be best effort; columns in base with no + corresponding key in from_json will be left null. + When processing json, if a column is specified more + than once, the last value is used. + + select * from json_populate_recordset(null::x, '[{"a":1,"b":2},{"a":3,"b":4}]') + + + a | b +---+--- + 1 | 2 + 3 | 4 + + + + + json_array_elements(json) + jsonb_array_elements(jsonb) + + SETOF json + SETOF jsonb + + + Expands a JSON array to a set of JSON values. + + SELECT * FROM json_array_elements('[1,true, [2,false]]') + + + value +----------- + 1 + true + [2,false] + + + + + json_array_elements_text(json) + jsonb_array_elements_text(jsonb) + + SETOF text + + Expands a JSON array to a set of text values. + + SELECT * FROM json_array_elements_text('["foo", "bar"]') + + + value +----------- + foo + bar + + + + + json_typeof(json) + jsonb_typeof(jsonb) + + text + + Returns the type of the outermost JSON value as a text string. The types are + object, array, string, number, + boolean, and null. (See note below regarding the + distinction between a JSON null and a SQL NULL.) + + json_typeof('-123.4') + number + - - json_to_record - json_to_record(json, nested_as_text bool) record - json_to_record returns an arbitrary record from a JSON object. As with all functions + Returns an arbitrary record from a JSON object. As with all functions returning 'record', the caller must explicitly define the structure of the record when making the call. The input JSON must be an object, not a scalar or an array. If nested_as_text is true, the function coerces nested complex elements to text. @@ -10526,14 +10671,11 @@ table2-mapping - - json_to_recordset - json_to_recordset(json, nested_as_text bool) setof record - json_to_recordset returns an arbitrary set of records from a JSON object. As with + Returns an arbitrary set of records from a JSON object. As with json_to_record, the structure of the record must be explicitly defined when making the call. However, with json_to_recordset the input JSON must be an array containing objects. nested_as_text works as with json_to_record. diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml new file mode 100644 index 0000000000..d7d6de8d28 --- /dev/null +++ b/doc/src/sgml/json.sgml @@ -0,0 +1,413 @@ + + + + <acronym>JSON</> Types + + + JSON + + + + JSONB + + + + JSON data types are for storing JSON (JavaScript Object Notation) + data, as specified in RFC + 7159. Such data can also be stored as text, but + both JSON data types have the advantage of enforcing that each + stored value is a valid JSON value. There are also related support + functions available; see . + + + + There are two JSON data types: json and jsonb. + Both accept almost identical sets of values as + input. The major practical difference is one of efficiency. The + json data type stores an exact copy of the the input text, + which processing functions must continually reparse, while + jsonb data is stored in a decomposed binary format that + makes it slightly less efficient to input due to added serialization + overhead, but significantly faster to process, since it never needs + reparsing. jsonb also supports advanced + GIN indexing, which is a further significant + advantage. + + + + The other difference between the types is that the json + type is guaranteed to contain an exact copy of the input, including + preservation of semantically insignificant white space, and the + order of keys within JSON objects (although jsonb will + preserve trailing zeros within a JSON number). Also, because the + exact text is kept, if a JSON object within the value contains the + same key more than once, and has been stored using the json + type, all the key/value pairs are kept. In that case, the + processing functions consider the last value as the operative one. + By contrast, jsonb does not preserve white space, does not + preserve the order of object keys, and does not keep duplicate + object keys. Only the last value for a key specified in the input + is kept. + + + + In general, most applications will prefer to store JSON data as + jsonb, unless there are quite specialized needs. + + + + PostgreSQL allows only one server + encoding per database. It is therefore not possible for the JSON + types to conform rigidly to the specification unless the server + encoding is UTF-8. Attempts to directly include characters which + cannot be represented in the server encoding will fail; conversely, + characters which can be represented in the server encoding but not + in UTF-8 will be allowed. \uXXXX escapes are + allowed regardless of the server encoding, and are checked only for + syntactic correctness. + + + + Mapping of RFC-7159/JSON Primitive Types to <productname>PostgreSQL</productname> Types +
+ Mapping of type correspondence, notes + + + + PostgreSQL type + RFC-7159/JSON primitive type + Notes + + + + + text + string + See general introductory notes on encoding and JSON + + + numeric + number + NaN and infinity values are disallowed + + + boolean + boolean + Only lowercase true and false values are accepted + + + unknown + null + SQL NULL is orthogonal. NULL semantics do not apply. + + + +
+ + Primitive types described by RFC 7159 are effectively + internally mapped onto native + PostgreSQL types. Therefore, there are + some very minor additional constraints on what constitutes valid + jsonb that do not apply to the json + type, or to JSON in the abstract, that pertain to limits on what + can be represented by the underlying type system. These + implementation-defined restrictions are permitted by + RFC 7159. However, in practice problems are far more + likely to occur in other implementations which internally + represent the number JSON primitive type as IEEE 754 + double precision floating point values, which RFC 7159 + explicitly anticipates and allows for. When using JSON as an + interchange format with such systems, the danger of losing numeric + precision in respect of data originally stored by + PostgreSQL should be considered. + + + Conversely, as noted above there are some minor restrictions on + the input format of JSON primitive types that do not apply to + corresponding PostgreSQL types. + + + + + + Querying <type>jsonb</type> documents effectively + + Representing data as JSON can be considerably more flexible than + the traditional relational data model, which is compelling in + environments where requirements are fluid. It is quite possible + for both approaches to co-exist and complement each other within + the same application. However, even for applications where maximal + flexibility is desired, it is still recommended that JSON documents + have a somewhat fixed structure. This structure is typically + unenforced (though enforcing some business rules declaratively is + possible), but makes it easier to write queries that usefully + summarize a set of documents (datums) in a table. + + + jsonb data is subject to the same concurrency control + considerations as any other datatype when stored in a table. + Although storing large documents is practicable, in order to ensure + correct behavior row-level locks are, quite naturally, aquired as + rows are updated. Consider keeping jsonb documents at a + manageable size in order to decrease lock contention among updating + transactions. Ideally, jsonb documents should each + represent an atomic datum that business rules dictate cannot + reasonably be further subdivided into smaller atomic datums that + can be independently modified. + + + + <type>jsonb</> Input and Output Syntax + + In effect, jsonb has an internal type system whose + implementation is defined in terms of several particular ordinary + PostgreSQL types. The SQL parser does + not have direct knowledge of the internal types that constitute a + jsonb. + + + The following are all valid jsonb expressions: + +-- Simple scalar/primitive value (explicitly required by RFC-7159) +SELECT '5'::jsonb; + +-- Array of heterogeneous, primitive-typed elements +SELECT '[1, 2, "foo", null]'::jsonb; + +-- Object of heterogeneous key/value pairs of primitive types +-- Note that key values are always strings +SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb; + + + + Note the distinction between scalar/primitive values as elements, + keys and values. + + + + <type>jsonb</> containment + + jsonb + containment + + + Testing containment is an important capability of + jsonb. There is no parallel set of facilities for the + json type. Containment is the ability to determine if + one jsonb document has contained within it another one. + jsonb is nested, and so containment semantics are nested; + technically, top-down, unordered subtree isomorphism + may be tested. Containment is conventionally tested using the + @> operator, which is made indexable by various + operator classes discussed later in this section. + + +-- Simple scalar/primitive values may contain only each other: +SELECT '"foo"'::jsonb @> '"foo"'::jsonb; + +-- The array on the right hand side is contained within the one on the +-- left hand side: +SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb; + +-- The object with a single pair on the right hand side is contained +-- within the object on the left hand side: +SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb":true}'::jsonb @> '{"version":9.4}'::jsonb; + +-- The array on the right hand side is not contained within the array +-- containing a nested array on the left hand side: +SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb; + +-- But with a layer of nesting, it is: +SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb; + + + It is both a sufficient and a necessary condition for nesting + levels to line up for one jsonb to contain + within it another. Under this definition, objects and arrays + cannot line up, not least because objects contain + key/value pairs, while arrays contain elements. + + + As a special exception to the general principle that nesting + levels should line up, an array may contain a raw scalar: + + +-- This array contains the raw scalar value: +SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb; +-- The special exception is not reciprocated -- non-containment is indicated here: +SELECT '"bar"'::jsonb @> '["bar"]'::jsonb; + + + Objects are better suited for testing containment when there is a + great deal of nesting involved, because unlike arrays they are + internally optimized for searching, and do not need to be searched + linearly within a single jsonb document. + + +-- The right-hand side object is contained in this example: +SELECT '{"p":1, "a":{"b":3, "q":11}, "i":77}'::jsonb @> '{"a":{"b":3}}'::jsonb; + + + The various containment operators, along with all other JSON + operators and support functions are documented fully within , . + + + + <type>jsonb</> GIN Indexing + + jsonb + indexes on + + + jsonb GIN indexes can be used to efficiently search among + more than one possible key/value pair within a single + jsonb datum/document, among a large number of such + documents within a column in a table (i.e. among many rows). + + + jsonb has GIN index support for the @>, + ?, ?& and ?| operators. + The default GIN operator class makes all these operators + indexable: + + +-- GIN index (default opclass) +CREATE INDEX idxgin ON api USING GIN (jdoc); + +-- GIN jsonb_hash_ops index +CREATE INDEX idxginh ON api USING GIN (jdoc jsonb_hash_ops); + + + The non-default GIN operator class jsonb_hash_ops + supports indexing the @> operator only. + + + Consider the example of a table that stores JSON documents + retrieved from a third-party web service, with a documented schema + definition. An example of a document retrieved from this web + service is as follows: + +{ + "guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a", + "name": "Angela Barton", + "is_active": true, + "company": "Magnafone", + "address": "178 Howard Place, Gulf, Washington, 702", + "registered": "2009-11-07T08:53:22 +08:00", + "latitude": 19.793713, + "longitude": 86.513373, + "tags": [ + "enim", + "aliquip", + "qui" + ] +} + + If a GIN index is created on the table that stores these + documents, api, on its jdoc + jsonb column, we can expect that queries like the + following may make use of the index: + +-- Note that both key and value have been specified +SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}'; + + However, the index could not be used for queries like the + following, due to the aforementioned nesting restriction: + +SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui'; + + Still, with judicious use of expressional indexing, the above + query can use an index scan. If there is a requirement to find + those records with a particular tag quickly, and the tags have a + high cardinality across all documents, defining an index as + follows is an effective approach to indexing: + +-- Note that the "jsonb -> text" operator can only be called on an +-- object, so as a consequence of creating this index the root "jdoc" +-- datum must be an object. This is enforced during insertion. +CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags')); + + + + Expressional indexes are discussed in . + + + For the most flexible approach in terms of what may be indexed, + sophisticated querying on nested structures is possible by + exploiting containment. At the cost of having to create an index + on the entire structure for each row, and not just a nested + subset, we may exploit containment semantics to get an equivalent + result with a non-expressional index on the entire jdoc + column, without ever having to create additional + expressional indexes against the document (provided only + containment will be tested). While the index will be considerably + larger than our expression index, it will also be much more + flexible, allowing arbitrary structured searching. Such an index + can generally be expected to help with a query like the following: + + +SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}'; + + + For full details of the semantics that these indexable operators + implement, see , . + + + + <type>jsonb</> non-default GIN operator class + + jsonb + indexes on + + + Although only the @> operator is made indexable, a + jsonb_hash_ops operator class GIN index has + some notable advantages over an equivalent GIN index of the + default GIN operator class for jsonb. Search + operations typically perform considerably better, and the on-disk + size of a jsonb_hash_ops operator class GIN + index can be much smaller. + + + + <type>jsonb</> B-Tree and hash indexing + + jsonb comparisons and related operations are + type-wise, in that the underlying + PostgreSQL datatype comparators are + invoked recursively, much like a traditional composite type. + + + jsonb also supports btree and hash + indexes. Ordering between jsonb datums is: + + Object > Array > Boolean > Number > String > Null + + Object with n pairs > object with n - 1 pairs + + Array with n elements > array with n - 1 elements + + Subsequently, individual primitive type comparators are invoked. + All comparisons of JSON primitive types occurs using the same + comparison rules as the underlying + PostgreSQL types. Strings are + compared lexically, using the default database collation. + Objects with equal numbers of pairs are compared: + + key-1, value-1, key-2 ... + + Note however that object keys are compared in their storage order, and in particular, + since shorter keys are stored before longer keys, this can lead to results that might be + unintuitive, such as: + { "aa": 1, "c": 1} > {"b": 1, "d": 1} + Similarly, arrays with equal numbers of elements are compared: + + element-1, element-2 ... + + + + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 053d7585ca..662040261e 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -825,6 +825,14 @@ CREATE OR REPLACE FUNCTION json_populate_recordset(base anyelement, from_json json, use_json_as_text boolean DEFAULT false) RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100 AS 'json_populate_recordset'; +CREATE OR REPLACE FUNCTION + jsonb_populate_record(base anyelement, from_json jsonb, use_json_as_text boolean DEFAULT false) + RETURNS anyelement LANGUAGE internal STABLE AS 'jsonb_populate_record'; + +CREATE OR REPLACE FUNCTION + jsonb_populate_recordset(base anyelement, from_json jsonb, use_json_as_text boolean DEFAULT false) + RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100 AS 'jsonb_populate_recordset'; + CREATE OR REPLACE FUNCTION pg_logical_slot_get_changes( IN slotname name, IN upto_lsn pg_lsn, IN upto_nchanges int, VARIADIC options text[] DEFAULT '{}', OUT location pg_lsn, OUT xid xid, OUT data text) diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 644687954b..6b23069e26 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 97a0e9f211..c34a1bb50b 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 0000000000..b30e79e425 --- /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 0000000000..4a6b8fd688 --- /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 0000000000..d6b1855c19 --- /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 0000000000..4a1d445130 --- /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 2320305608..f80eaeb1c6 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 */ @@ -372,6 +461,51 @@ json_object_field(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +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) { @@ -388,6 +522,74 @@ json_object_field_text(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +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) { @@ -403,6 +605,44 @@ json_array_element(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +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) { @@ -418,6 +658,69 @@ json_array_element_text(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +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) { @@ -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). @@ -908,25 +1352,44 @@ json_each(PG_FUNCTION_ARGS) return each_worker(fcinfo, false); } +Datum +jsonb_each(PG_FUNCTION_ARGS) +{ + return each_worker_jsonb(fcinfo, false); +} + Datum json_each_text(PG_FUNCTION_ARGS) { return each_worker(fcinfo, true); } -static inline Datum -each_worker(PG_FUNCTION_ARGS, bool as_text) +Datum +jsonb_each_text(PG_FUNCTION_ARGS) { - text *json = PG_GETARG_TEXT_P(0); - JsonLexContext *lex = makeJsonLexContext(json, true); - JsonSemAction *sem; - ReturnSetInfo *rsi; - MemoryContext old_cxt; - TupleDesc tupdesc; - EachState *state; + return each_worker_jsonb(fcinfo, true); +} - state = palloc0(sizeof(EachState)); - sem = palloc0(sizeof(JsonSemAction)); +static inline Datum +each_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_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; @@ -947,6 +1410,138 @@ each_worker(PG_FUNCTION_ARGS, bool as_text) 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)); + + 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; + + (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,8 +2005,15 @@ 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) { @@ -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; @@ -1655,6 +2445,134 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype) * is pushed down into the semantic action handlers so it's done * 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) { @@ -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 b78451dda0..64eb0f8d16 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -626,6 +626,44 @@ numeric_out_sci(Numeric num, int scale) return str; } +/* + * 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 * diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 7c1bc1d04c..2623113557 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -777,6 +777,33 @@ DATA(insert ( 4017 25 25 12 s 665 4000 0 )); DATA(insert ( 4017 25 25 14 s 667 4000 0 )); DATA(insert ( 4017 25 25 15 s 666 4000 0 )); +/* + * btree jsonb_ops + */ +DATA(insert ( 4033 3802 3802 1 s 3242 403 0 )); +DATA(insert ( 4033 3802 3802 2 s 3244 403 0 )); +DATA(insert ( 4033 3802 3802 3 s 3240 403 0 )); +DATA(insert ( 4033 3802 3802 4 s 3245 403 0 )); +DATA(insert ( 4033 3802 3802 5 s 3243 403 0 )); + +/* + * hash jsonb ops + */ +DATA(insert ( 4034 3802 3802 1 s 3240 405 0 )); + +/* + * GIN jsonb ops + */ +DATA(insert ( 4036 3802 3802 7 s 3246 2742 0 )); +DATA(insert ( 4036 3802 25 9 s 3247 2742 0 )); +DATA(insert ( 4036 3802 1009 10 s 3248 2742 0 )); +DATA(insert ( 4036 3802 1009 11 s 3249 2742 0 )); + +/* + * GIN jsonb hash ops + */ +DATA(insert ( 4037 3802 3802 7 s 3246 2742 0 )); + /* * SP-GiST range_ops */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index e3773e9759..b28dd563a8 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -138,6 +138,7 @@ DATA(insert ( 3522 3500 3500 1 3514 )); DATA(insert ( 3626 3614 3614 1 3622 )); DATA(insert ( 3683 3615 3615 1 3668 )); DATA(insert ( 3901 3831 3831 1 3870 )); +DATA(insert ( 4033 3802 3802 1 4044 )); /* hash */ @@ -175,6 +176,7 @@ DATA(insert ( 2235 1033 1033 1 329 )); DATA(insert ( 2969 2950 2950 1 2963 )); DATA(insert ( 3523 3500 3500 1 3515 )); DATA(insert ( 3903 3831 3831 1 3902 )); +DATA(insert ( 4034 3802 3802 1 4045 )); /* gist */ @@ -387,7 +389,16 @@ DATA(insert ( 3659 3614 3614 3 3657 )); DATA(insert ( 3659 3614 3614 4 3658 )); DATA(insert ( 3659 3614 3614 5 2700 )); DATA(insert ( 3659 3614 3614 6 3921 )); - +DATA(insert ( 4036 3802 3802 1 3480 )); +DATA(insert ( 4036 3802 3802 2 3482 )); +DATA(insert ( 4036 3802 3802 3 3483 )); +DATA(insert ( 4036 3802 3802 4 3484 )); +DATA(insert ( 4036 3802 3802 6 3488 )); +DATA(insert ( 4037 3802 3802 1 351 )); +DATA(insert ( 4037 3802 3802 2 3485 )); +DATA(insert ( 4037 3802 3802 3 3486 )); +DATA(insert ( 4037 3802 3802 4 3487 )); +DATA(insert ( 4037 3802 3802 6 3489 )); /* sp-gist */ DATA(insert ( 3474 3831 3831 1 3469 )); diff --git a/src/include/catalog/pg_cast.h b/src/include/catalog/pg_cast.h index 3544d0a6ad..e037957472 100644 --- a/src/include/catalog/pg_cast.h +++ b/src/include/catalog/pg_cast.h @@ -359,4 +359,8 @@ DATA(insert ( 1560 1560 1685 i f )); DATA(insert ( 1562 1562 1687 i f )); DATA(insert ( 1700 1700 1703 i f )); +/* json to/from jsonb */ +DATA(insert ( 114 3802 0 e i )); +DATA(insert ( 3802 114 0 e i )); + #endif /* PG_CAST_H */ diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index 6860637482..63a40a8412 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -228,5 +228,9 @@ DATA(insert ( 4000 range_ops PGNSP PGUID 3474 3831 t 0 )); DATA(insert ( 4000 quad_point_ops PGNSP PGUID 4015 600 t 0 )); DATA(insert ( 4000 kd_point_ops PGNSP PGUID 4016 600 f 0 )); DATA(insert ( 4000 text_ops PGNSP PGUID 4017 25 t 0 )); +DATA(insert ( 403 jsonb_ops PGNSP PGUID 4033 3802 t 0 )); +DATA(insert ( 405 jsonb_ops PGNSP PGUID 4034 3802 t 0 )); +DATA(insert ( 2742 jsonb_ops PGNSP PGUID 4036 3802 t 25 )); +DATA(insert ( 2742 jsonb_hash_ops PGNSP PGUID 4037 3802 f 23 )); #endif /* PG_OPCLASS_H */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index e07d6d9ef9..ac09034f3d 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -1769,8 +1769,41 @@ DATA(insert OID = 3966 ( "#>" PGNSP PGUID b f f 114 1009 114 0 0 json_extrac DESCR("get value from json with path elements"); DATA(insert OID = 3967 ( "#>>" PGNSP PGUID b f f 114 1009 25 0 0 json_extract_path_text_op - - )); DESCR("get value from json as text with path elements"); - - +DATA(insert OID = 3211 ( "->" PGNSP PGUID b f f 3802 25 3802 0 0 jsonb_object_field - - )); +DESCR("get jsonb object field"); +DATA(insert OID = 3477 ( "->>" PGNSP PGUID b f f 3802 25 25 0 0 jsonb_object_field_text - - )); +DESCR("get jsonb object field as text"); +DATA(insert OID = 3212 ( "->" PGNSP PGUID b f f 3802 23 3802 0 0 jsonb_array_element - - )); +DESCR("get jsonb array element"); +DATA(insert OID = 3481 ( "->>" PGNSP PGUID b f f 3802 23 25 0 0 jsonb_array_element_text - - )); +DESCR("get jsonb array element as text"); +DATA(insert OID = 3213 ( "#>" PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_extract_path_op - - )); +DESCR("get value from jsonb with path elements"); +DATA(insert OID = 3206 ( "#>>" PGNSP PGUID b f f 3802 1009 25 0 0 jsonb_extract_path_text_op - - )); +DESCR("get value from jsonb as text with path elements"); +DATA(insert OID = 3240 ( "=" PGNSP PGUID b t t 3802 3802 16 3240 3241 jsonb_eq eqsel eqjoinsel )); +DESCR("equal"); +DATA(insert OID = 3241 ( "<>" PGNSP PGUID b f f 3802 3802 16 3241 3240 jsonb_ne neqsel neqjoinsel )); +DESCR("not equal"); +DATA(insert OID = 3242 ( "<" PGNSP PGUID b f f 3802 3802 16 3243 3245 jsonb_lt scalarltsel scalarltjoinsel )); +DESCR("less than"); +DATA(insert OID = 3243 ( ">" PGNSP PGUID b f f 3802 3802 16 3242 3244 jsonb_gt scalargtsel scalargtjoinsel )); +DESCR("greater than"); +DATA(insert OID = 3244 ( "<=" PGNSP PGUID b f f 3802 3802 16 3245 3243 jsonb_le scalarltsel scalarltjoinsel )); +DESCR("less than or equal to"); +DATA(insert OID = 3245 ( ">=" PGNSP PGUID b f f 3802 3802 16 3244 3242 jsonb_ge scalargtsel scalargtjoinsel )); +DESCR("greater than or equal to"); +/* No commutator? */ +DATA(insert OID = 3246 ( "@>" PGNSP PGUID b f f 3802 3802 16 0 3250 jsonb_contains contsel contjoinsel )); +DESCR("contains"); +DATA(insert OID = 3247 ( "?" PGNSP PGUID b f f 3802 25 16 0 0 jsonb_exists contsel contjoinsel )); +DESCR("exists"); +DATA(insert OID = 3248 ( "?|" PGNSP PGUID b f f 3802 1009 16 0 0 jsonb_exists_any contsel contjoinsel )); +DESCR("exists any"); +DATA(insert OID = 3249 ( "?&" PGNSP PGUID b f f 3802 1009 16 0 0 jsonb_exists_all contsel contjoinsel )); +DESCR("exists all"); +DATA(insert OID = 3250 ( "<@" PGNSP PGUID b f f 3802 3802 16 0 3246 jsonb_contained contsel contjoinsel )); +DESCR("contained"); /* * function prototypes diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index 229dcb1288..775be86c1a 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -147,6 +147,11 @@ DATA(insert OID = 3474 ( 4000 range_ops PGNSP PGUID )); DATA(insert OID = 4015 ( 4000 quad_point_ops PGNSP PGUID )); DATA(insert OID = 4016 ( 4000 kd_point_ops PGNSP PGUID )); DATA(insert OID = 4017 ( 4000 text_ops PGNSP PGUID )); +DATA(insert OID = 4033 ( 403 jsonb_ops PGNSP PGUID )); +DATA(insert OID = 4034 ( 405 jsonb_ops PGNSP PGUID )); +DATA(insert OID = 4035 ( 783 jsonb_ops PGNSP PGUID )); +DATA(insert OID = 4036 ( 2742 jsonb_ops PGNSP PGUID )); +DATA(insert OID = 4037 ( 2742 jsonb_hash_ops PGNSP PGUID )); #define TEXT_SPGIST_FAM_OID 4017 #endif /* PG_OPFAMILY_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 4bd23fc5fd..56f0f11ebe 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4173,6 +4173,8 @@ DESCR("get value from json as text with path elements"); DATA(insert OID = 3954 ( json_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "114 1009" _null_ _null_ "{from_json,path_elems}" _null_ json_extract_path_text _null_ _null_ _null_ )); DATA(insert OID = 3955 ( json_array_elements PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 114 "114" "{114,114}" "{i,o}" "{from_json,value}" _null_ json_array_elements _null_ _null_ _null_ )); DESCR("key value pairs of a json object"); +DATA(insert OID = 3969 ( json_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" "{114,25}" "{i,o}" "{from_json,value}" _null_ json_array_elements_text _null_ _null_ _null_ )); +DESCR("elements of json array"); DATA(insert OID = 3956 ( json_array_length PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "114" _null_ _null_ _null_ _null_ json_array_length _null_ _null_ _null_ )); DESCR("length of json array"); DATA(insert OID = 3957 ( json_object_keys PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" _null_ _null_ _null_ _null_ json_object_keys _null_ _null_ _null_ )); @@ -4191,8 +4193,6 @@ DATA(insert OID = 3205 ( json_to_recordset PGNSP PGUID 12 1 100 0 0 f f f f f DESCR("get set of records with fields from a json array of objects"); DATA(insert OID = 3968 ( json_typeof PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "114" _null_ _null_ _null_ _null_ json_typeof _null_ _null_ _null_ )); DESCR("get the type of a json value"); -DATA(insert OID = 3969 ( json_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "114" "{114,25}" "{i,o}" "{from_json,value}" _null_ json_array_elements_text _null_ _null_ _null_ )); -DESCR("elements of json array"); /* uuid */ DATA(insert OID = 2952 ( uuid_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2950 "2275" _null_ _null_ _null_ _null_ uuid_in _null_ _null_ _null_ )); @@ -4510,6 +4510,83 @@ DESCR("I/O"); DATA(insert OID = 3774 ( regdictionarysend PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "3769" _null_ _null_ _null_ _null_ regdictionarysend _null_ _null_ _null_ )); DESCR("I/O"); +/* jsonb */ +DATA(insert OID = 3806 ( jsonb_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3802 "2275" _null_ _null_ _null_ _null_ jsonb_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3805 ( jsonb_recv PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3802 "2281" _null_ _null_ _null_ _null_ jsonb_recv _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3804 ( jsonb_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3802" _null_ _null_ _null_ _null_ jsonb_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 3803 ( jsonb_send PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "3802" _null_ _null_ _null_ _null_ jsonb_send _null_ _null_ _null_ )); +DESCR("I/O"); + +DATA(insert OID = 3478 ( jsonb_object_field PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ jsonb_object_field _null_ _null_ _null_ )); +DATA(insert OID = 3214 ( jsonb_object_field_text PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 25" _null_ _null_ "{from_json, field_name}" _null_ jsonb_object_field_text _null_ _null_ _null_ )); +DATA(insert OID = 3215 ( jsonb_array_element PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 23" _null_ _null_ "{from_json, element_index}" _null_ jsonb_array_element _null_ _null_ _null_ )); +DATA(insert OID = 3216 ( jsonb_array_element_text PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 23" _null_ _null_ "{from_json, element_index}" _null_ jsonb_array_element_text _null_ _null_ _null_ )); +DATA(insert OID = 3217 ( jsonb_extract_path PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 3802 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ )); +DESCR("get value from jsonb with path elements"); +DATA(insert OID = 3939 ( jsonb_extract_path_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 3802 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path _null_ _null_ _null_ )); +DATA(insert OID = 3940 ( jsonb_extract_path_text PGNSP PGUID 12 1 0 25 0 f f f f t f i 2 0 25 "3802 1009" "{3802,1009}" "{i,v}" "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ )); +DESCR("get value from jsonb as text with path elements"); +DATA(insert OID = 3218 ( jsonb_extract_path_text_op PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 25 "3802 1009" _null_ _null_ "{from_json,path_elems}" _null_ jsonb_extract_path_text _null_ _null_ _null_ )); +DATA(insert OID = 3219 ( jsonb_array_elements PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 3802 "3802" "{3802,3802}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements _null_ _null_ _null_ )); +DESCR("elements of a jsonb array"); +DATA(insert OID = 3465 ( jsonb_array_elements_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" "{3802,25}" "{i,o}" "{from_json,value}" _null_ jsonb_array_elements_text _null_ _null_ _null_ )); +DESCR("elements of jsonb array"); +DATA(insert OID = 3207 ( jsonb_array_length PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3802" _null_ _null_ _null_ _null_ jsonb_array_length _null_ _null_ _null_ )); +DESCR("length of jsonb array"); +DATA(insert OID = 3931 ( jsonb_object_keys PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_object_keys _null_ _null_ _null_ )); +DESCR("get jsonb object keys"); +DATA(insert OID = 3208 ( jsonb_each PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 2249 "3802" "{3802,25,3802}" "{i,o,o}" "{from_json,key,value}" _null_ jsonb_each _null_ _null_ _null_ )); +DESCR("key value pairs of a jsonb object"); +DATA(insert OID = 3932 ( jsonb_each_text PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 2249 "3802" "{3802,25,25}" "{i,o,o}" "{from_json,key,value}" _null_ jsonb_each_text _null_ _null_ _null_ )); +DESCR("key value pairs of a jsonb object"); +DATA(insert OID = 3209 ( jsonb_populate_record PGNSP PGUID 12 1 0 0 0 f f f f f f s 3 0 2283 "2283 3802 16" _null_ _null_ _null_ _null_ jsonb_populate_record _null_ _null_ _null_ )); +DESCR("get record fields from a jsonb object"); +DATA(insert OID = 3475 ( jsonb_populate_recordset PGNSP PGUID 12 1 100 0 0 f f f f f t s 3 0 2283 "2283 3802 16" _null_ _null_ _null_ _null_ jsonb_populate_recordset _null_ _null_ _null_ )); +DESCR("get set of records with fields from a jsonb array of objects"); +DATA(insert OID = 3210 ( jsonb_typeof PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "3802" _null_ _null_ _null_ _null_ jsonb_typeof _null_ _null_ _null_ )); +DESCR("get the type of a jsonb value"); +DATA(insert OID = 4038 ( jsonb_ne PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_ne _null_ _null_ _null_ )); +DATA(insert OID = 4039 ( jsonb_lt PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_lt _null_ _null_ _null_ )); +DATA(insert OID = 4040 ( jsonb_gt PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_gt _null_ _null_ _null_ )); +DATA(insert OID = 4041 ( jsonb_le PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_le _null_ _null_ _null_ )); +DATA(insert OID = 4042 ( jsonb_ge PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_ge _null_ _null_ _null_ )); +DATA(insert OID = 4043 ( jsonb_eq PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_eq _null_ _null_ _null_ )); +DATA(insert OID = 4044 ( jsonb_cmp PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "3802 3802" _null_ _null_ _null_ _null_ jsonb_cmp _null_ _null_ _null_ )); +DESCR("less-equal-greater"); +DATA(insert OID = 4045 ( jsonb_hash PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 23 "3802" _null_ _null_ _null_ _null_ jsonb_hash _null_ _null_ _null_ )); +DESCR("hash"); +DATA(insert OID = 4046 ( jsonb_contains PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_contains _null_ _null_ _null_ )); +DESCR("implementation of @> operator"); +DATA(insert OID = 4047 ( jsonb_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 25" _null_ _null_ _null_ _null_ jsonb_exists _null_ _null_ _null_ )); +DESCR("implementation of ? operator"); +DATA(insert OID = 4048 ( jsonb_exists_any PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 1009" _null_ _null_ _null_ _null_ jsonb_exists_any _null_ _null_ _null_ )); +DESCR("implementation of ?| operator"); +DATA(insert OID = 4049 ( jsonb_exists_all PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 1009" _null_ _null_ _null_ _null_ jsonb_exists_all _null_ _null_ _null_ )); +DESCR("implementation of ?& operator"); +DATA(insert OID = 4050 ( jsonb_contained PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "3802 3802" _null_ _null_ _null_ _null_ jsonb_contained _null_ _null_ _null_ )); +DESCR("implementation of <@ operator"); +DATA(insert OID = 3480 ( gin_compare_jsonb PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "25 25" _null_ _null_ _null_ _null_ gin_compare_jsonb _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3482 ( gin_extract_jsonb PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3483 ( gin_extract_jsonb_query PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 2281 "2277 2281 21 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_query _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3484 ( gin_consistent_jsonb PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 2277 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_consistent_jsonb _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3488 ( gin_triconsistent_jsonb PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ gin_triconsistent_jsonb _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3485 ( gin_extract_jsonb_hash PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_hash _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3486 ( gin_extract_jsonb_query_hash PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 2281 "2277 2281 21 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_extract_jsonb_query_hash _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3487 ( gin_consistent_jsonb_hash PGNSP PGUID 12 1 0 0 0 f f f f t f i 8 0 16 "2281 21 2277 23 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gin_consistent_jsonb_hash _null_ _null_ _null_ )); +DESCR("GIN support"); +DATA(insert OID = 3489 ( gin_triconsistent_jsonb_hash PGNSP PGUID 12 1 0 0 0 f f f f t f i 7 0 16 "2281 21 2277 23 2281 2281 2281" _null_ _null_ _null_ _null_ gin_triconsistent_jsonb_hash _null_ _null_ _null_ )); +DESCR("GIN support"); + /* txid */ DATA(insert OID = 2939 ( txid_snapshot_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2970 "2275" _null_ _null_ _null_ _null_ txid_snapshot_in _null_ _null_ _null_ )); DESCR("I/O"); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 4c060f54b3..92d50bb36e 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -606,6 +606,12 @@ DATA(insert OID = 3645 ( _tsquery PGNSP PGUID -1 f b A f t \054 0 3615 0 array_ DATA(insert OID = 3735 ( _regconfig PGNSP PGUID -1 f b A f t \054 0 3734 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ )); DATA(insert OID = 3770 ( _regdictionary PGNSP PGUID -1 f b A f t \054 0 3769 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ )); +/* jsonb */ +DATA(insert OID = 3802 ( jsonb PGNSP PGUID -1 f b C f t \054 0 0 3807 jsonb_in jsonb_out jsonb_recv jsonb_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); +DESCR("Binary JSON"); +#define JSONBOID 3802 +DATA(insert OID = 3807 ( _jsonb PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ )); + DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ )); DESCR("txid snapshot"); DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ )); diff --git a/src/include/funcapi.h b/src/include/funcapi.h index 9982e590b8..3610fc80d8 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -293,6 +293,15 @@ extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx); PG_RETURN_DATUM(_result); \ } while (0) +#define SRF_RETURN_NEXT_NULL(_funcctx) \ + do { \ + ReturnSetInfo *rsi; \ + (_funcctx)->call_cntr++; \ + rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ + rsi->isDone = ExprMultipleResult; \ + PG_RETURN_NULL(); \ + } while (0) + #define SRF_RETURN_DONE(_funcctx) \ do { \ ReturnSetInfo *rsi; \ diff --git a/src/include/utils/json.h b/src/include/utils/json.h index baf751e999..b5e947bd7a 100644 --- a/src/include/utils/json.h +++ b/src/include/utils/json.h @@ -64,4 +64,19 @@ extern Datum json_populate_recordset(PG_FUNCTION_ARGS); extern Datum json_to_record(PG_FUNCTION_ARGS); extern Datum json_to_recordset(PG_FUNCTION_ARGS); +extern Datum jsonb_object_field(PG_FUNCTION_ARGS); +extern Datum jsonb_object_field_text(PG_FUNCTION_ARGS); +extern Datum jsonb_array_element(PG_FUNCTION_ARGS); +extern Datum jsonb_array_element_text(PG_FUNCTION_ARGS); +extern Datum jsonb_extract_path(PG_FUNCTION_ARGS); +extern Datum jsonb_extract_path_text(PG_FUNCTION_ARGS); +extern Datum jsonb_object_keys(PG_FUNCTION_ARGS); +extern Datum jsonb_array_length(PG_FUNCTION_ARGS); +extern Datum jsonb_each(PG_FUNCTION_ARGS); +extern Datum jsonb_each_text(PG_FUNCTION_ARGS); +extern Datum jsonb_array_elements_text(PG_FUNCTION_ARGS); +extern Datum jsonb_array_elements(PG_FUNCTION_ARGS); +extern Datum jsonb_populate_record(PG_FUNCTION_ARGS); +extern Datum jsonb_populate_recordset(PG_FUNCTION_ARGS); + #endif /* JSON_H */ diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 32fb0f79e7..7a4fbfe454 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -100,11 +100,17 @@ typedef struct JsonSemAction extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); /* - * constructor for JsonLexContext, with or without strval element. + * constructors for JsonLexContext, with or without strval element. * If supplied, the strval element will contain a de-escaped version of * the lexeme. However, doing this imposes a performance penalty, so * it should be avoided if the de-escaped lexeme is not required. + * + * If you already have the json as a text* value, use the first of these + * functions, otherwise use makeJsonLexContextCstringLen(). */ extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes); +extern JsonLexContext *makeJsonLexContextCstringLen(char *json, + int len, + bool need_escapes); #endif /* JSONAPI_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h new file mode 100644 index 0000000000..a70cbd5940 --- /dev/null +++ b/src/include/utils/jsonb.h @@ -0,0 +1,320 @@ +/*------------------------------------------------------------------------- + * + * jsonb.h + * Declarations for jsonb data type support. + * + * Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * src/include/utils/jsonb.h + * + *------------------------------------------------------------------------- + */ +#ifndef __JSONB_H__ +#define __JSONB_H__ + +#include "lib/stringinfo.h" +#include "utils/array.h" +#include "utils/numeric.h" + +/* + * JB_CMASK is used to extract count of items + * + * It's not possible to get more than 2^28 items into an Jsonb. + */ +#define JB_CMASK 0x0FFFFFFF + +#define JB_FSCALAR 0x10000000 +#define JB_FOBJECT 0x20000000 +#define JB_FARRAY 0x40000000 + +/* Get information on varlena Jsonb */ +#define JB_ROOT_COUNT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_CMASK) +#define JB_ROOT_IS_SCALAR(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FSCALAR) +#define JB_ROOT_IS_OBJECT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FOBJECT) +#define JB_ROOT_IS_ARRAY(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FARRAY) + +/* Jentry macros */ +#define JENTRY_POSMASK 0x0FFFFFFF +#define JENTRY_ISFIRST 0x80000000 +#define JENTRY_TYPEMASK (~(JENTRY_POSMASK | JENTRY_ISFIRST)) +#define JENTRY_ISSTRING 0x00000000 +#define JENTRY_ISNUMERIC 0x10000000 +#define JENTRY_ISNEST 0x20000000 +#define JENTRY_ISNULL 0x40000000 +#define JENTRY_ISBOOL (JENTRY_ISNUMERIC | JENTRY_ISNEST) +#define JENTRY_ISFALSE JENTRY_ISBOOL +#define JENTRY_ISTRUE (JENTRY_ISBOOL | 0x40000000) +/* Note possible multiple evaluations, also access to prior array element */ +#define JBE_ISFIRST(je_) (((je_).header & JENTRY_ISFIRST) != 0) +#define JBE_ISSTRING(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISSTRING) +#define JBE_ISNUMERIC(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNUMERIC) +#define JBE_ISNEST(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNEST) +#define JBE_ISNULL(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISNULL) +#define JBE_ISBOOL(je_) (((je_).header & JENTRY_TYPEMASK & JENTRY_ISBOOL) == JENTRY_ISBOOL) +#define JBE_ISBOOL_TRUE(je_) (((je_).header & JENTRY_TYPEMASK) == JENTRY_ISTRUE) +#define JBE_ISBOOL_FALSE(je_) (JBE_ISBOOL(je_) && !JBE_ISBOOL_TRUE(je_)) + +/* Get offset for Jentry */ +#define JBE_ENDPOS(je_) ((je_).header & JENTRY_POSMASK) +#define JBE_OFF(je_) (JBE_ISFIRST(je_) ? 0 : JBE_ENDPOS((&(je_))[-1])) +#define JBE_LEN(je_) (JBE_ISFIRST(je_) ? \ + JBE_ENDPOS(je_) \ + : JBE_ENDPOS(je_) - JBE_ENDPOS((&(je_))[-1])) + +/* Flags indicating a stage of sequential Jsonb processing */ +#define WJB_DONE 0x000 +#define WJB_KEY 0x001 +#define WJB_VALUE 0x002 +#define WJB_ELEM 0x004 +#define WJB_BEGIN_ARRAY 0x008 +#define WJB_END_ARRAY 0x010 +#define WJB_BEGIN_OBJECT 0x020 +#define WJB_END_OBJECT 0x040 + +/* + * When using a GIN index for jsonb, we choose to index both keys and values. + * The storage format is text, with K, or V prepended to the string to indicate + * key/element or value/element. + * + * Jsonb Keys and string array elements are treated equivalently when + * serialized to text index storage. One day we may wish to create an opclass + * that only indexes values, but for now keys and values are stored in GIN + * indexes in a way that doesn't really consider their relationship to each + * other. + */ +#define JKEYELEM 'K' +#define JVAL 'V' + +#define JsonbContainsStrategyNumber 7 +#define JsonbExistsStrategyNumber 9 +#define JsonbExistsAnyStrategyNumber 10 +#define JsonbExistsAllStrategyNumber 11 + +/* Convenience macros */ +#define DatumGetJsonb(d) ((Jsonb *) PG_DETOAST_DATUM(d)) +#define JsonbGetDatum(p) PointerGetDatum(p) +#define PG_GETARG_JSONB(x) DatumGetJsonb(PG_GETARG_DATUM(x)) +#define PG_RETURN_JSONB(x) PG_RETURN_POINTER(x) + +typedef struct JsonbPair JsonbPair; +typedef struct JsonbValue JsonbValue; +typedef char* JsonbSuperHeader; + +/* + * Jsonbs are varlena objects, so must meet the varlena convention that the + * first int32 of the object contains the total object size in bytes. Be sure + * to use VARSIZE() and SET_VARSIZE() to access it, though! + * + * Jsonb is the on-disk representation, in contrast to the in-memory JsonbValue + * representation. Often, JsonbValues are just shims through which a Jsonb + * buffer is accessed, but they can also be deep copied and passed around. + * + * We have an abstraction called a "superheader". This is a pointer that + * conventionally points to the first item after our 4-byte uncompressed + * varlena header, from which we can read flags using bitwise operations. + * + * Frequently, we pass a superheader reference to a function, and it doesn't + * matter if it points to just after the start of a Jsonb, or to a temp buffer. + */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint32 superheader; + /* (array of JEntry follows, size determined using uint32 superheader) */ +} Jsonb; + +/* + * JEntry: there is one of these for each key _and_ value for objects. Arrays + * have one per element. + * + * The position offset points to the _end_ so that we can get the length by + * subtraction from the previous entry. The JENTRY_ISFIRST flag indicates if + * there is a previous entry. + */ +typedef struct +{ + uint32 header; /* Shares some flags with superheader */ +} JEntry; + +#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \ + (jsonbval)->type <= jbvBool) + +/* + * JsonbValue: In-memory representation of Jsonb. This is a convenient + * deserialized representation, that can easily support using the anonymous + * union across underlying types during manipulation. The Jsonb on-disk + * representation has various alignment considerations. + */ +struct JsonbValue +{ + enum + { + /* Scalar types */ + jbvNull = 0x0, + jbvString, + jbvNumeric, + jbvBool, + /* Composite types */ + jbvArray = 0x10, + jbvObject, + /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ + jbvBinary + } type; /* Influences sort order */ + + int estSize; /* Estimated size of node (including + * subnodes) */ + + union + { + Numeric numeric; + bool boolean; + struct + { + int len; + char *val; /* Not necessarily null-terminated */ + } string; /* String primitive type */ + + struct + { + int nElems; + JsonbValue *elems; + bool rawScalar; /* Top-level "raw scalar" array? */ + } array; /* Array container type */ + + struct + { + int nPairs; /* 1 pair, 2 elements */ + JsonbPair *pairs; + } object; /* Associative container type */ + + struct + { + int len; + char *data; + } binary; + }; +}; + +/* + * Pair within an Object. + * + * Pairs with duplicate keys are de-duplicated. We store the order for the + * benefit of doing so in a well-defined way with respect to the original + * observed order (which is "last observed wins"). This is only used briefly + * when originally constructing a Jsonb. + */ +struct JsonbPair +{ + JsonbValue key; /* Must be a jbvString */ + JsonbValue value; /* May be of any type */ + uint32 order; /* preserves order of pairs with equal keys */ +}; + +/* Conversion state used when parsing Jsonb from text, or for type coercion */ +typedef struct JsonbParseState +{ + JsonbValue contVal; + Size size; + struct JsonbParseState *next; +} JsonbParseState; + +/* + * JsonbIterator holds details of the type for each iteration. It also stores a + * Jsonb varlena buffer, which can be directly accessed in some contexts. + */ +typedef enum +{ + jbi_start = 0x0, + jbi_key, + jbi_value, + jbi_elem +} JsonbIterState; + +typedef struct JsonbIterator +{ + /* Jsonb varlena buffer (may or may not be root) */ + char *buffer; + + /* Current value */ + uint32 containerType; /* Never of value JB_FSCALAR, since + * scalars will appear in pseudo-arrays */ + uint32 nElems; /* Number of elements in metaArray + * (will be nPairs for objects) */ + bool isScalar; /* Pseudo-array scalar value? */ + JEntry *meta; + + /* Current item in buffer (up to nElems, but must * 2 for objects) */ + int i; + + /* + * Data proper. Note that this points just past end of "meta" array. We + * use its metadata (Jentrys) with JBE_OFF() macro to find appropriate + * offsets into this array. + */ + char *dataProper; + + /* Private state */ + JsonbIterState state; + + struct JsonbIterator *parent; +} JsonbIterator; + +/* I/O routines */ +extern Datum jsonb_in(PG_FUNCTION_ARGS); +extern Datum jsonb_out(PG_FUNCTION_ARGS); +extern Datum jsonb_recv(PG_FUNCTION_ARGS); +extern Datum jsonb_send(PG_FUNCTION_ARGS); +extern Datum jsonb_typeof(PG_FUNCTION_ARGS); + +/* Indexing-related ops */ +extern Datum jsonb_exists(PG_FUNCTION_ARGS); +extern Datum jsonb_exists_any(PG_FUNCTION_ARGS); +extern Datum jsonb_exists_all(PG_FUNCTION_ARGS); +extern Datum jsonb_contains(PG_FUNCTION_ARGS); +extern Datum jsonb_contained(PG_FUNCTION_ARGS); +extern Datum jsonb_ne(PG_FUNCTION_ARGS); +extern Datum jsonb_lt(PG_FUNCTION_ARGS); +extern Datum jsonb_gt(PG_FUNCTION_ARGS); +extern Datum jsonb_le(PG_FUNCTION_ARGS); +extern Datum jsonb_ge(PG_FUNCTION_ARGS); +extern Datum jsonb_eq(PG_FUNCTION_ARGS); +extern Datum jsonb_cmp(PG_FUNCTION_ARGS); +extern Datum jsonb_hash(PG_FUNCTION_ARGS); + +/* GIN support functions */ +extern Datum gin_compare_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS); +extern Datum gin_consistent_jsonb(PG_FUNCTION_ARGS); +extern Datum gin_triconsistent_jsonb(PG_FUNCTION_ARGS); +/* GIN hash opclass functions */ +extern Datum gin_extract_jsonb_hash(PG_FUNCTION_ARGS); +extern Datum gin_extract_jsonb_query_hash(PG_FUNCTION_ARGS); +extern Datum gin_consistent_jsonb_hash(PG_FUNCTION_ARGS); +extern Datum gin_triconsistent_jsonb_hash(PG_FUNCTION_ARGS); + +/* Support functions */ +extern int compareJsonbSuperHeaderValue(JsonbSuperHeader a, + JsonbSuperHeader b); +extern JsonbValue *findJsonbValueFromSuperHeader(JsonbSuperHeader sheader, + uint32 flags, + uint32 *lowbound, + JsonbValue *key); +extern JsonbValue *getIthJsonbValueFromSuperHeader(JsonbSuperHeader sheader, + uint32 i); +extern JsonbValue *pushJsonbValue(JsonbParseState ** pstate, int seq, + JsonbValue *scalarVal); +extern JsonbIterator *JsonbIteratorInit(JsonbSuperHeader buffer); +extern int JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, + bool skipNested); +extern Jsonb *JsonbValueToJsonb(JsonbValue *val); +extern bool JsonbDeepContains(JsonbIterator ** val, + JsonbIterator ** mContained); +extern JsonbValue *arrayToJsonbSortedArray(ArrayType *a); +extern void JsonbHashScalarValue(const JsonbValue * scalarVal, uint32 * hash); + +/* jsonb.c support function */ +extern char *JsonbToCString(StringInfo out, JsonbSuperHeader in, + int estimated_len); + +#endif /* __JSONB_H__ */ diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index c230e0f415..d298718f7d 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -58,5 +58,6 @@ typedef struct NumericData *Numeric; extern bool numeric_is_nan(Numeric num); int32 numeric_maximum_size(int32 typmod); extern char *numeric_out_sci(Numeric num, int scale); +extern char *numeric_normalize(Numeric num); #endif /* _PG_NUMERIC_H_ */ diff --git a/src/test/regress/data/jsonb.data b/src/test/regress/data/jsonb.data new file mode 100644 index 0000000000..1352ebe3ac --- /dev/null +++ b/src/test/regress/data/jsonb.data @@ -0,0 +1,1009 @@ +{"line":1, "date":"CB", "node":"AA"} +{"cleaned":false, "status":59, "line":2, "disabled":false, "node":"CBB"} +{"indexed":true, "status":35, "line":3, "disabled":false, "wait":"CAA", "subtitle":"BA", "user":"CCA"} +{"line":4, "disabled":true, "space":"BB"} +{"cleaned":false, "line":5, "wait":"BB", "query":"CAC", "coauthors":"ACA", "node":"CBA"} +{"world":"CB", "query":"CBC", "indexed":false, "line":6, "pos":92, "date":"AAB", "space":"CB", "coauthors":"ACA", "node":"CBC"} +{"state":98, "org":43, "line":7, "pos":97} +{"auth":"BB", "title":"CAC", "query":"BA", "status":94, "line":8, "coauthors":"BBB"} +{"auth":"BAC", "title":"CAA", "wait":"CA", "bad":true, "query":"AA", "indexed":true, "line":9, "pos":56} +{"title":"AAC", "bad":true, "user":"AAB", "query":"AC", "line":10, "node":"AB"} +{"world":"CAC", "user":"AB", "query":"ACA", "indexed":true, "line":11, "space":"CB"} +{"line":12, "pos":72, "abstract":"BBA", "space":"AAC"} +{} +{"world":"CC", "query":"AA", "line":14, "disabled":false, "date":"CAC", "coauthors":"AB"} +{"org":68, "title":"BBB", "query":"BAC", "line":15, "public":false} +{"org":73, "user":"AA", "indexed":true, "line":16, "date":"CCC", "public":true, "coauthors":"AB"} +{"indexed":false, "line":17} +{"state":23, "auth":"BCC", "org":38, "status":28, "line":18, "disabled":false, "abstract":"CB"} +{"state":99, "auth":"CA", "indexed":true, "line":19, "date":"BA"} +{"wait":"CBA", "user":"BBA", "indexed":true, "line":20, "disabled":false, "abstract":"BA", "date":"ABA"} +{"org":10, "query":"AC", "indexed":false, "line":21, "disabled":true, "abstract":"CA", "pos":44} +{"state":65, "title":"AC", "user":"AAC", "cleaned":true, "status":93, "line":22, "abstract":"ABC", "node":"CCC"} +{"subtitle":"AC", "user":"CCC", "line":23} +{"state":67, "world":"ACB", "bad":true, "user":"CB", "line":24, "disabled":true} +{} +{"state":65, "title":"CBC", "wait":"AAC", "bad":true, "query":"ACA", "line":26, "disabled":false, "space":"CA"} +{"auth":"BAA", "state":68, "indexed":true, "line":27, "space":"BA"} +{"indexed":false, "line":28, "disabled":true, "space":"CC", "node":"BB"} +{"auth":"BAB", "org":80, "title":"BBA", "query":"BBC", "status":3, "line":29} +{"title":"AC", "status":16, "cleaned":true, "line":30} +{"state":39, "world":"AAB", "user":"BB", "line":31, "disabled":true} +{"wait":"BC", "bad":false, "query":"AA", "line":32, "coauthors":"CAC"} +{"line":33, "pos":97} +{"title":"AA", "world":"CCA", "wait":"CC", "bad":true, "status":86, "line":34, "disabled":true, "node":"ACA"} +{} +{"world":"BCC", "title":"ACB", "org":61, "status":99, "cleaned":true, "line":36, "pos":76, "space":"ACC", "coauthors":"AA", "node":"CB"} +{"title":"CAA", "cleaned":false, "line":37, "abstract":"ACA", "node":"BC"} +{"auth":"BC", "title":"BA", "world":"ACA", "indexed":true, "line":38, "abstract":"AAA", "public":true} +{"org":90, "line":39, "public":false} +{"state":16, "indexed":true, "line":40, "pos":53} +{"auth":"AAB", "wait":"CAC", "status":44, "line":41} +{"subtitle":"ACA", "bad":true, "line":42} +{"org":19, "world":"BC", "user":"ABA", "indexed":false, "line":43, "disabled":true, "pos":48, "abstract":"CAB", "space":"CCB"} +{"indexed":false, "line":44} +{"indexed":true, "line":45} +{"status":84, "line":46, "date":"CCC"} +{"state":94, "title":"BAB", "bad":true, "user":"BBB", "indexed":true, "line":47, "public":false} +{"org":90, "subtitle":"BAC", "query":"CAC", "cleaned":false, "line":48, "disabled":true, "abstract":"CC", "pos":17, "space":"BCA"} +{"world":"CBC", "line":49} +{"org":24, "line":50, "date":"CA", "public":false} +{"world":"BC", "indexed":true, "status":44, "line":51, "pos":59, "date":"BA", "public":true} +{"org":98, "line":52} +{"title":"CA", "world":"ABC", "subtitle":"CBB", "line":53, "abstract":"BBA", "date":"ACB", "node":"CA"} +{"user":"BAB", "cleaned":true, "line":54} +{"subtitle":"CAA", "line":55, "disabled":false, "pos":55, "abstract":"AB", "public":false, "coauthors":"AA"} +{"wait":"CC", "user":"CC", "cleaned":true, "line":56, "pos":73, "node":"ABC"} +{"title":"BCC", "wait":"ABC", "indexed":true, "line":57, "disabled":false} +{"org":42, "title":"BB", "line":58, "disabled":true, "public":true, "coauthors":"BCC"} +{"wait":"CAB", "title":"CCB", "query":"BAC", "status":66, "line":59, "disabled":true} +{"user":"CAC", "line":60} +{"user":"BBB", "line":61, "disabled":false, "pos":31} +{"org":18, "line":62, "coauthors":"CCC", "node":"CA"} +{"line":63, "coauthors":"AB"} +{"org":25, "wait":"CA", "line":64, "abstract":"BA", "date":"BBB"} +{"title":"CB", "wait":"CC", "bad":false, "user":"BBB", "line":65, "abstract":"ACA", "public":true} +{"line":66, "coauthors":"AC"} +{"state":20, "wait":"CCB", "bad":true, "line":67, "abstract":"CB"} +{"state":79, "wait":"BAC", "bad":false, "status":11, "line":68, "abstract":"BC", "public":true, "coauthors":"CBA"} +{"state":39, "title":"CCA", "bad":false, "query":"BBA", "line":69, "pos":42, "public":false} +{"title":"BC", "subtitle":"CA", "query":"BC", "line":70} +{} +{"bad":true, "query":"BBB", "line":72} +{"state":35, "world":"CC", "bad":false, "line":73, "space":"BB", "public":false} +{"title":"ACC", "wait":"CAB", "subtitle":"CB", "status":19, "line":74, "disabled":false, "space":"BAA", "coauthors":"CBC", "node":"AC"} +{"subtitle":"BCB", "indexed":false, "status":83, "line":75, "public":true} +{"state":32, "line":76, "disabled":false, "pos":66, "space":"CC"} +{"state":43, "cleaned":true, "line":77} +{} +{"state":97, "wait":"CBA", "indexed":false, "cleaned":false, "line":79, "abstract":"CB", "date":"ACC", "public":false} +{"user":"AAB", "line":80, "pos":85, "date":"AC"} +{"world":"AC", "wait":"CC", "subtitle":"AAB", "bad":false, "cleaned":false, "line":81, "pos":91, "node":"CCC"} +{} +{"org":87, "bad":false, "user":"AAC", "query":"CCC", "line":83, "disabled":false, "abstract":"AC", "date":"CCA", "public":false} +{"state":50, "line":84} +{"wait":"AA", "subtitle":"AA", "query":"BB", "status":97, "line":85, "disabled":true, "abstract":"CB"} +{} +{"subtitle":"CA", "query":"BC", "line":87} +{} +{"title":"CC", "line":89, "disabled":false, "pos":49, "date":"CCB", "space":"CB", "node":"BB"} +{"auth":"CC", "wait":"AA", "title":"BC", "bad":true, "line":90} +{"state":37, "org":85, "indexed":false, "line":91, "space":"CAA", "public":true, "coauthors":"BA"} +{"wait":"BBB", "title":"BBC", "org":95, "subtitle":"AC", "line":92, "pos":23, "date":"AC", "public":true, "space":"BBC"} +{"org":48, "user":"AC", "line":93, "space":"CCC"} +{} +{"state":77, "wait":"ABA", "subtitle":"AC", "user":"BA", "status":43, "line":95, "public":false} +{"title":"CA", "indexed":true, "status":26, "line":96} +{"auth":"BCA", "subtitle":"ACC", "user":"CA", "line":97, "disabled":false, "node":"ACB"} +{"query":"BB", "line":98, "coauthors":"AB"} +{} +{"auth":"AA", "title":"ACB", "org":58, "subtitle":"AC", "bad":false, "cleaned":false, "line":100, "space":"ACC", "public":true} +{"subtitle":"AAB", "bad":false, "line":101, "public":true} +{"subtitle":"AAA", "indexed":false, "cleaned":false, "line":102, "disabled":true, "pos":35} +{} +{"world":"CAC", "org":10, "query":"AAA", "cleaned":true, "status":79, "indexed":true, "line":104, "pos":65, "public":false, "node":"BAB"} +{"bad":false, "line":105, "abstract":"BA", "node":"CBB"} +{"world":"BB", "wait":"BAA", "title":"ACA", "line":106, "date":"CBC", "space":"BA"} +{"state":92, "wait":"CAC", "title":"AAA", "bad":false, "line":107, "abstract":"CBC", "date":"BCC", "public":false} +{"title":"CCC", "indexed":true, "line":108, "abstract":"ACB", "public":false, "coauthors":"ABB"} +{"auth":"BB", "query":"ACC", "status":68, "line":109} +{"user":"CC", "cleaned":false, "indexed":true, "line":110, "date":"BAA", "space":"BCB"} +{"auth":"CC", "org":4, "wait":"BAC", "bad":false, "indexed":false, "line":111, "pos":55, "node":"BBC"} +{"line":112, "disabled":true} +{"org":66, "cleaned":true, "indexed":false, "line":113, "pos":96} +{"world":"CA", "title":"ACA", "org":83, "query":"BAC", "user":"BBC", "indexed":false, "line":114} +{"subtitle":"BCC", "line":115, "space":"AA", "public":true, "node":"CBA"} +{"state":77, "status":23, "line":116} +{"bad":false, "status":4, "line":117, "node":"CC"} +{"state":99, "title":"BCC", "query":"AC", "status":98, "line":118, "date":"BA"} +{"status":55, "line":119, "public":false, "coauthors":"BBA", "node":"BCA"} +{"query":"CAA", "status":40, "indexed":false, "line":120, "disabled":false, "coauthors":"CA"} +{"title":"BBC", "org":82, "subtitle":"ACB", "line":121, "abstract":"BB", "node":"CC"} +{"state":66, "world":"AB", "subtitle":"BA", "query":"CB", "line":122, "abstract":"BBC", "pos":65, "date":"BAB"} +{"state":96, "title":"CBC", "status":44, "line":123, "abstract":"BA", "space":"ACA", "node":"AAC"} +{"auth":"CA", "state":59, "bad":false, "cleaned":false, "line":124, "pos":41, "date":"BBA", "coauthors":"ABB"} +{"wait":"ACC", "line":125} +{"org":30, "wait":"CBB", "subtitle":"CCA", "cleaned":true, "line":126, "date":"AC", "node":"ABC"} +{} +{"auth":"BBA", "org":66, "subtitle":"CCB", "bad":true, "cleaned":false, "line":128, "abstract":"BB", "public":true, "coauthors":"BA"} +{"subtitle":"AC", "bad":false, "user":"BAA", "line":129, "date":"BCB", "node":"BAC"} +{"wait":"CC", "subtitle":"CA", "line":130, "disabled":false, "pos":49, "node":"BA"} +{"indexed":false, "line":131, "pos":79, "date":"AAA", "node":"CAC"} +{"wait":"AC", "world":"CB", "title":"AAA", "user":"ABC", "indexed":false, "status":15, "line":132, "coauthors":"BA"} +{"state":96, "bad":true, "line":133, "disabled":false, "space":"BAC", "coauthors":"ABA"} +{"world":"BAC", "line":134} +{"title":"CCC", "line":135, "coauthors":"CC"} +{"cleaned":true, "line":136} +{"bad":true, "query":"CCA", "user":"CA", "cleaned":false, "line":137, "disabled":true} +{} +{"world":"CC", "subtitle":"BBB", "line":139} +{"wait":"CA", "status":2, "line":140} +{"world":"BC", "bad":false, "user":"BBC", "query":"ACB", "line":141, "pos":33, "space":"ACA"} +{"state":92, "title":"CA", "bad":true, "query":"AB", "line":142, "abstract":"BA", "date":"ABB", "space":"BC", "coauthors":"CAA"} +{"state":79, "query":"AB", "user":"CCA", "indexed":true, "cleaned":true, "line":143, "public":true} +{"org":37, "query":"CA", "cleaned":true, "line":144, "disabled":true, "date":"CC"} +{"wait":"AC", "title":"CBA", "user":"AAA", "status":24, "line":145, "date":"CBC", "public":false, "coauthors":"BAC", "node":"ACC"} +{"user":"CA", "indexed":true, "line":146, "disabled":false, "coauthors":"BA"} +{"wait":"BC", "org":35, "bad":false, "query":"CBB", "line":147, "date":"AAA", "public":false, "space":"BBB"} +{"org":56, "user":"AB", "indexed":true, "line":148} +{} +{"title":"CBB", "org":78, "subtitle":"CBA", "bad":true, "user":"AAB", "line":150, "disabled":true, "abstract":"BAC"} +{"world":"CCA", "query":"BC", "cleaned":true, "indexed":false, "line":151, "abstract":"BC", "pos":43, "coauthors":"AB", "node":"CBA"} +{"auth":"ABA", "status":13, "line":152, "date":"AA"} +{"world":"CA", "line":153, "space":"CBC"} +{"world":"BA", "user":"BBB", "status":72, "line":154} +{"auth":"ABB", "line":155, "disabled":true, "node":"BBC"} +{"world":"BBB", "bad":false, "line":156, "abstract":"CBC"} +{"line":157, "pos":60, "node":"ACC"} +{"line":158, "node":"CC"} +{"line":159, "public":true} +{} +{"query":"BA", "status":53, "cleaned":false, "line":161, "public":true} +{"line":162, "date":"CC"} +{} +{"title":"BC", "bad":false, "query":"CC", "line":164, "abstract":"CCB", "date":"BA"} +{"status":36, "line":165} +{"title":"AB", "bad":false, "status":64, "line":166, "abstract":"AB", "coauthors":"AA", "node":"AA"} +{"wait":"AA", "line":167} +{"subtitle":"CBC", "user":"AC", "cleaned":false, "line":168, "disabled":true, "coauthors":"BAB", "node":"CC"} +{"state":34, "status":73, "cleaned":true, "line":169, "abstract":"BC", "public":false, "space":"BBC", "node":"BAA"} +{"state":10, "auth":"BBB", "bad":true, "indexed":false, "status":34, "line":170, "abstract":"BC"} +{"subtitle":"AAA", "bad":false, "user":"ACA", "status":53, "line":171, "disabled":false, "date":"AAA"} +{"subtitle":"CB", "query":"CC", "indexed":true, "line":172, "node":"BBC"} +{"state":5, "world":"ABC", "bad":false, "line":173, "public":false} +{"subtitle":"AC", "line":174} +{"auth":"AC", "org":72, "query":"CA", "indexed":false, "cleaned":true, "line":175, "disabled":true, "pos":54} +{"title":"BCB", "bad":false, "line":176, "pos":35, "coauthors":"AAC", "node":"ABB"} +{"title":"BB", "cleaned":true, "status":26, "line":177} +{"state":61, "wait":"BB", "world":"CB", "query":"BAB", "line":178, "abstract":"BB", "date":"CBB", "space":"CA", "node":"AB"} +{"wait":"CA", "cleaned":false, "indexed":true, "line":179, "space":"CBC"} +{"org":68, "line":180} +{"wait":"ABB", "subtitle":"CCC", "cleaned":true, "line":181, "abstract":"BC", "coauthors":"BA"} +{"title":"ACA", "subtitle":"AAB", "line":182, "node":"BAC"} +{} +{} +{"subtitle":"BA", "query":"BBB", "indexed":true, "cleaned":true, "line":185, "node":"BCC"} +{"org":6, "title":"BCC", "user":"BA", "line":186, "pos":67, "abstract":"CBA", "coauthors":"CBB", "node":"CBC"} +{"org":50, "title":"CAB", "subtitle":"CB", "query":"CBB", "line":187, "coauthors":"CA", "node":"CC"} +{"bad":false, "line":188, "node":"CCB"} +{"org":4, "world":"AAC", "query":"CAC", "line":189, "pos":90, "node":"AC"} +{"state":86, "line":190, "pos":79} +{"org":98, "title":"AAC", "cleaned":true, "line":191, "space":"BC", "coauthors":"AA"} +{"wait":"CAA", "bad":false, "user":"BC", "status":23, "line":192, "disabled":true, "date":"CA", "coauthors":"BBB"} +{"status":26, "line":193, "disabled":true} +{"world":"CA", "subtitle":"CCC", "query":"ABB", "status":86, "line":194, "pos":97, "space":"CAC"} +{"cleaned":true, "line":195} +{"state":53, "org":84, "wait":"BC", "query":"BCC", "line":196, "disabled":true, "abstract":"AAC", "node":"CAC"} +{"state":25, "status":70, "cleaned":false, "line":197, "disabled":true, "space":"AA", "public":false} +{"org":82, "subtitle":"AAC", "line":198} +{"org":87, "bad":true, "status":69, "line":199, "public":false} +{"wait":"CC", "org":60, "subtitle":"BCA", "bad":true, "cleaned":false, "indexed":true, "line":200, "date":"BA"} +{"state":9, "world":"CAA", "org":78, "user":"ACB", "cleaned":true, "line":201, "disabled":true, "abstract":"ACC", "public":false} +{"state":50, "world":"AAA", "title":"CAA", "user":"AB", "status":37, "line":202, "disabled":false} +{"org":36, "subtitle":"CB", "query":"BAA", "status":35, "line":203, "abstract":"CC"} +{"auth":"CCC", "bad":true, "query":"CB", "status":84, "line":204, "disabled":false, "date":"BB"} +{"auth":"AC", "query":"BA", "indexed":false, "line":205, "date":"AAB", "space":"ABB"} +{"state":30, "world":"CCA", "query":"CC", "user":"BAA", "line":206} +{"title":"CAB", "wait":"BAB", "bad":true, "query":"BCB", "indexed":true, "status":48, "cleaned":true, "line":207, "node":"ACB"} +{"state":97, "subtitle":"BC", "status":99, "line":208, "abstract":"CB"} +{"title":"CA", "world":"BBA", "bad":true, "indexed":false, "cleaned":false, "status":82, "line":209, "disabled":false, "pos":44, "space":"ACA"} +{"line":210, "public":true} +{"line":211, "space":"BBC", "node":"AAA"} +{"wait":"BAA", "org":50, "line":212, "abstract":"BB", "public":true, "space":"AB"} +{"line":213, "pos":57, "date":"CC", "space":"AC"} +{"state":23, "user":"BAB", "query":"BCB", "line":214, "abstract":"BAB"} +{"world":"ACB", "org":21, "line":215, "abstract":"AC", "public":false} +{"state":14, "wait":"ACB", "org":79, "title":"BB", "subtitle":"BA", "line":216} +{"wait":"BC", "line":217, "date":"BB"} +{"wait":"AC", "user":"BB", "indexed":false, "status":83, "line":218} +{"auth":"BC", "org":9, "user":"BA", "status":31, "line":219, "disabled":false} +{"state":80, "world":"BA", "wait":"CA", "line":220, "pos":65, "node":"CAC"} +{"wait":"AC", "subtitle":"ABB", "status":79, "indexed":true, "line":221, "abstract":"AC", "pos":33, "space":"BA"} +{"state":69, "org":83, "world":"CBC", "subtitle":"CAC", "cleaned":false, "line":222, "space":"BC", "node":"CCA"} +{"line":223, "abstract":"BC"} +{} +{"world":"BB", "title":"BC", "bad":false, "query":"BBC", "cleaned":false, "line":225, "disabled":false, "public":true} +{"line":226, "date":"AC"} +{"auth":"CB", "subtitle":"AB", "indexed":false, "status":2, "line":227, "pos":53, "space":"AB", "coauthors":"BCA"} +{"title":"ABA", "org":36, "line":228, "space":"AA"} +{"world":"AB", "line":229, "pos":78, "date":"BC", "space":"CC"} +{"wait":"BBC", "org":47, "cleaned":true, "status":5, "line":230, "pos":2, "date":"CCA"} +{"line":231, "coauthors":"CB"} +{"state":1, "user":"CAA", "cleaned":false, "line":232, "date":"BA", "public":true, "coauthors":"AAA", "node":"BCC"} +{"auth":"AB", "world":"CAC", "query":"BC", "cleaned":true, "line":233, "pos":47, "space":"AB", "node":"AB"} +{"title":"CAA", "line":234, "pos":9, "public":true, "node":"AB"} +{"auth":"CCA", "title":"AA", "org":6, "subtitle":"CA", "cleaned":true, "status":12, "indexed":false, "line":235, "space":"ABB"} +{"auth":"CA", "bad":false, "query":"BC", "status":61, "cleaned":false, "line":236, "disabled":true, "public":true} +{"user":"BCB", "line":237, "pos":70, "node":"CBA"} +{"query":"CCB", "line":238, "disabled":true, "coauthors":"BAB", "node":"BC"} +{"auth":"AC", "org":73, "title":"CA", "bad":false, "status":94, "line":239, "abstract":"CC"} +{"subtitle":"BC", "indexed":false, "line":240, "disabled":true} +{"auth":"AAC", "org":73, "title":"CB", "bad":true, "query":"CA", "cleaned":true, "line":241, "disabled":false, "public":false} +{"line":242, "public":false} +{"auth":"AC", "title":"BC", "status":61, "line":243, "disabled":false} +{"auth":"ABB", "bad":false, "indexed":false, "line":244, "abstract":"BAB", "date":"ABC", "coauthors":"BC"} +{"query":"BA", "line":245, "disabled":false, "space":"BAB"} +{"world":"BCC", "bad":false, "indexed":false, "line":246, "disabled":true, "pos":80, "public":false, "coauthors":"BC"} +{"indexed":true, "line":247} +{"wait":"CCA", "subtitle":"CBB", "bad":false, "line":248, "pos":83, "public":false, "space":"BA"} +{} +{"auth":"ABA", "org":13, "title":"BA", "bad":false, "indexed":true, "line":250, "disabled":false, "abstract":"BBA", "date":"AB"} +{"state":37, "title":"AAA", "bad":false, "line":251, "disabled":false, "coauthors":"CBC"} +{"auth":"ACB", "world":"AC", "title":"CAA", "subtitle":"BCA", "bad":false, "status":32, "line":252, "pos":84} +{"query":"BA", "indexed":false, "status":0, "line":253, "abstract":"CCB", "pos":48, "date":"AC", "space":"AAC"} +{"subtitle":"BBA", "line":254, "node":"AAA"} +{"query":"AC", "user":"CAA", "status":13, "line":255, "public":true, "coauthors":"BCC"} +{"auth":"AAA", "state":31, "line":256} +{} +{} +{"wait":"AC", "query":"AAA", "cleaned":true, "indexed":false, "line":259, "pos":89, "coauthors":"BCA", "node":"BC"} +{"world":"CC", "query":"BB", "line":260} +{} +{"org":99, "bad":false, "user":"ABA", "line":262, "abstract":"BA", "coauthors":"BCC"} +{"auth":"CAC", "world":"CBC", "subtitle":"CA", "bad":false, "status":22, "line":263, "pos":4, "public":true, "node":"BB"} +{"wait":"BB", "subtitle":"BCC", "indexed":true, "line":264, "node":"CAC"} +{"subtitle":"BB", "query":"CBB", "line":265} +{"state":35, "query":"AA", "line":266, "coauthors":"AAA"} +{"status":6, "line":267, "pos":66} +{"auth":"BAA", "subtitle":"CCA", "bad":false, "query":"CCB", "line":268, "public":true, "space":"CAB", "node":"CAC"} +{"world":"AC", "org":58, "user":"AC", "line":269, "node":"AB"} +{"auth":"BCB", "org":36, "title":"AB", "line":270, "abstract":"CAB", "date":"CAB", "public":true, "coauthors":"CB", "node":"AB"} +{"cleaned":true, "line":271} +{"world":"ACC", "cleaned":true, "status":11, "line":272, "disabled":false, "abstract":"AA", "space":"BCA", "node":"BA"} +{"cleaned":true, "line":273, "pos":50, "public":true} +{"status":95, "line":274, "abstract":"BB", "coauthors":"AC"} +{"auth":"BCC", "state":80, "cleaned":true, "line":275, "abstract":"AC"} +{"wait":"BA", "line":276} +{"org":62, "subtitle":"CAA", "query":"BA", "user":"BCC", "indexed":false, "line":277, "disabled":false, "abstract":"ACA", "date":"AB"} +{"org":63, "bad":true, "line":278, "pos":26, "coauthors":"BA"} +{"auth":"CBB", "indexed":false, "line":279, "pos":40, "space":"CA", "coauthors":"CC"} +{"auth":"BA", "line":280, "abstract":"AAA", "public":true, "coauthors":"CAC"} +{"org":10, "status":16, "line":281, "date":"CCC", "space":"AC"} +{"org":76, "user":"BBC", "indexed":false, "line":282, "pos":56, "node":"CBA"} +{"auth":"CA", "subtitle":"AB", "query":"AA", "indexed":true, "line":283, "disabled":false, "coauthors":"ABC", "node":"CAA"} +{"title":"BA", "status":91, "line":284, "pos":7, "coauthors":"BB"} +{"wait":"CCA", "line":285, "public":true} +{"world":"AC", "line":286, "disabled":true} +{"line":287, "abstract":"AAA"} +{"user":"CCB", "status":50, "line":288, "public":false} +{"state":41, "world":"CCC", "query":"AA", "line":289, "disabled":true, "pos":49, "public":false} +{"wait":"CBC", "line":290, "abstract":"CCA", "space":"BBC"} +{"auth":"CCB", "world":"BAB", "user":"CCC", "status":93, "line":291, "pos":77, "node":"BAC"} +{"wait":"BCC", "org":8, "user":"AC", "cleaned":true, "line":292, "disabled":true, "pos":67, "date":"AA"} +{"org":56, "query":"BCA", "line":293, "pos":81, "coauthors":"AAA", "node":"CAB"} +{"world":"CB", "subtitle":"CBC", "bad":true, "query":"ACB", "indexed":false, "line":294, "pos":58, "date":"BC", "node":"CB"} +{"wait":"BC", "user":"CA", "line":295} +{"world":"ABA", "wait":"BA", "user":"BB", "status":65, "line":296, "pos":45, "date":"BB"} +{} +{} +{"auth":"BA", "user":"AA", "indexed":false, "line":299, "space":"ABA", "public":false, "coauthors":"BC"} +{"line":300, "space":"ABA"} +{"state":36, "org":16, "world":"BBC", "status":13, "line":301, "public":false} +{"subtitle":"CB", "user":"BC", "line":302, "date":"AA", "coauthors":"CAC"} +{"wait":"CBC", "indexed":true, "cleaned":true, "line":303, "date":"ACC", "public":true} +{"user":"CAC", "status":81, "line":304, "node":"CAB"} +{"title":"CBB", "org":89, "subtitle":"CAA", "user":"CCA", "indexed":true, "line":305} +{"state":10, "title":"CBA", "org":66, "cleaned":true, "line":306, "pos":59, "coauthors":"CAC"} +{} +{"auth":"AAA", "world":"AC", "wait":"ACA", "subtitle":"BAA", "status":64, "line":308, "node":"CCA"} +{"state":31, "world":"CCC", "title":"BCB", "cleaned":false, "status":11, "line":309, "disabled":true, "date":"AA"} +{"title":"BC", "subtitle":"CB", "indexed":false, "line":310, "disabled":true, "abstract":"BA", "space":"ACA"} +{"wait":"ABB", "cleaned":true, "indexed":false, "line":311, "space":"CAB"} +{} +{"subtitle":"CA", "line":313} +{"org":91, "title":"CAB", "line":314, "date":"CA"} +{} +{"state":65, "line":316, "node":"CC"} +{"line":317, "space":"AA"} +{} +{"wait":"AA", "indexed":true, "line":319} +{"wait":"BB", "org":42, "world":"AC", "subtitle":"ACC", "indexed":true, "line":320, "disabled":true} +{} +{"auth":"CAC", "line":322} +{} +{"line":324, "pos":38, "space":"CC", "node":"BBC"} +{"title":"CC", "line":325, "public":true, "coauthors":"BAC", "node":"ACC"} +{"world":"CC", "subtitle":"BBC", "bad":false, "user":"BA", "line":326, "date":"AAA", "space":"AA"} +{"state":81, "title":"BC", "wait":"BA", "indexed":false, "status":48, "line":327, "coauthors":"AB"} +{"line":328, "space":"ABB"} +{"line":329, "date":"CCA"} +{} +{"auth":"BB", "world":"BAB", "subtitle":"BA", "query":"ABB", "line":331, "disabled":true, "date":"AAA", "node":"BC"} +{"auth":"ABA", "title":"CC", "user":"CBA", "line":332, "disabled":true, "space":"ACC"} +{"org":98, "subtitle":"ACB", "line":333, "abstract":"BC", "public":false, "coauthors":"BC", "node":"ABA"} +{} +{"world":"BC", "subtitle":"BAC", "user":"AB", "query":"BAA", "cleaned":true, "line":335, "space":"AC", "node":"BAA"} +{"state":76, "indexed":true, "cleaned":false, "line":336, "node":"CAC"} +{"org":95, "status":84, "line":337} +{} +{"world":"BBA", "title":"BCC", "subtitle":"ACB", "query":"BA", "line":339, "space":"ABC", "node":"AC"} +{"title":"CBB", "user":"CBA", "cleaned":true, "line":340, "public":true, "space":"CB", "coauthors":"CAB"} +{"wait":"AA", "status":82, "line":341} +{"world":"CC", "line":342} +{"auth":"BAB", "title":"CAC", "query":"BCC", "indexed":true, "line":343} +{"org":77, "world":"BAC", "subtitle":"AA", "user":"ABA", "line":344} +{"state":99, "org":56, "world":"CC", "title":"CAB", "wait":"CB", "subtitle":"BCC", "line":345, "pos":65} +{"state":68, "org":97, "title":"AA", "indexed":true, "line":346, "node":"CC"} +{"state":3, "title":"CBC", "user":"BAA", "status":98, "line":347, "disabled":true, "pos":96, "date":"BBA"} +{"auth":"BAA", "world":"ABB", "line":348, "disabled":false, "abstract":"ACA", "pos":66, "space":"CCC", "coauthors":"CBB", "node":"BC"} +{} +{"status":54, "line":350} +{"wait":"CC", "query":"ABA", "user":"AB", "status":76, "cleaned":false, "line":351, "abstract":"CBA"} +{"line":352, "disabled":true, "public":false} +{"state":93, "org":92, "status":88, "line":353, "space":"AB", "coauthors":"CB"} +{"org":34, "wait":"ABC", "world":"CBA", "bad":false, "query":"BB", "indexed":false, "line":354, "date":"CB", "public":true} +{"wait":"CBA", "title":"CAC", "cleaned":true, "indexed":true, "line":355, "pos":9, "date":"CAA"} +{"user":"BC", "indexed":false, "cleaned":true, "status":73, "line":356, "disabled":true, "space":"CB"} +{"state":20, "cleaned":false, "line":357, "pos":28, "abstract":"CCB", "space":"BC"} +{"state":17, "wait":"ABC", "query":"CB", "cleaned":false, "status":4, "line":358, "disabled":false} +{} +{"state":83, "world":"CC", "org":53, "cleaned":false, "status":64, "line":360, "abstract":"CBC", "coauthors":"BC"} +{"title":"BB", "indexed":false, "line":361} +{"state":49, "wait":"BCA", "line":362} +{"world":"CCC", "title":"CA", "query":"CCC", "cleaned":true, "line":363, "space":"AA", "coauthors":"AAC"} +{"state":8, "wait":"BBB", "line":364, "pos":70, "public":false, "space":"BAA", "coauthors":"AB"} +{"state":20, "indexed":false, "status":87, "cleaned":true, "line":365, "public":true} +{} +{"state":92, "title":"CCC", "subtitle":"CAB", "status":39, "line":367} +{"state":54, "org":38, "line":368} +{} +{"auth":"ACA", "subtitle":"CBC", "status":52, "line":370, "date":"ACC", "public":true} +{"indexed":true, "line":371, "pos":98, "node":"CBA"} +{"world":"BA", "status":40, "line":372, "coauthors":"AA"} +{} +{"query":"BA", "indexed":false, "cleaned":true, "line":374, "date":"BCC"} +{"query":"CA", "indexed":true, "line":375, "public":false} +{"auth":"CCA", "wait":"BBC", "bad":false, "status":91, "line":376, "abstract":"BBC", "date":"ABA"} +{"user":"BA", "query":"CB", "status":86, "indexed":false, "line":377, "pos":83, "abstract":"BCC", "space":"CBC", "public":true} +{"title":"ACA", "org":15, "wait":"CBC", "status":85, "line":378} +{"state":57, "bad":true, "line":379, "abstract":"BC", "date":"CAC"} +{"world":"CC", "cleaned":true, "line":380} +{"title":"CB", "subtitle":"AC", "line":381, "public":false} +{} +{} +{"status":12, "line":384, "coauthors":"CC"} +{"auth":"BAC", "bad":false, "line":385, "abstract":"CBB", "public":false, "space":"BBC"} +{} +{} +{"world":"BBC", "bad":true, "status":71, "cleaned":false, "line":388, "node":"BB"} +{"cleaned":false, "line":389} +{"state":73, "line":390} +{"wait":"BB", "org":5, "subtitle":"BAA", "bad":false, "indexed":false, "line":391, "public":false, "node":"BAA"} +{"auth":"CCC", "org":51, "bad":false, "cleaned":true, "line":392, "space":"AC", "node":"CC"} +{} +{"line":394, "abstract":"ACC", "public":true} +{"org":44, "subtitle":"BAC", "query":"BAC", "line":395} +{"wait":"BC", "line":396} +{"state":68, "world":"AB", "title":"ABB", "user":"CBC", "cleaned":false, "indexed":true, "line":397, "abstract":"BA", "pos":11} +{"world":"CA", "title":"AB", "subtitle":"BC", "user":"BCB", "line":398} +{"bad":true, "query":"BCC", "line":399} +{"wait":"BB", "user":"BB", "cleaned":true, "indexed":false, "line":400, "date":"BC", "public":false} +{} +{"wait":"BA", "line":402} +{"title":"AC", "subtitle":"BCB", "query":"BA", "line":403} +{} +{"auth":"BA", "org":19, "query":"CCB", "line":405, "pos":82, "date":"CAA"} +{"state":26, "world":"CB", "subtitle":"AB", "cleaned":false, "line":406, "disabled":true, "date":"AC"} +{} +{"state":11, "bad":true, "indexed":true, "line":408, "pos":79, "abstract":"BA", "date":"CB", "space":"BBA"} +{"auth":"AC", "status":59, "line":409} +{"org":15, "line":410, "disabled":true, "date":"BAC", "space":"CCA"} +{} +{} +{"state":65, "world":"AB", "status":69, "line":413, "space":"BA"} +{} +{"title":"CCB", "line":415} +{"title":"BAB", "subtitle":"CA", "indexed":false, "line":416, "public":true} +{"wait":"CAB", "user":"CAB", "cleaned":true, "line":417, "date":"BC", "coauthors":"BBA"} +{"subtitle":"ABA", "user":"BB", "query":"AA", "indexed":true, "line":418, "pos":8, "space":"BB", "coauthors":"CBA"} +{"state":11, "indexed":true, "line":419, "node":"AA"} +{"state":86, "cleaned":false, "line":420, "pos":2, "node":"CBC"} +{"org":73, "line":421, "disabled":false} +{"query":"BAC", "user":"CB", "status":69, "line":422} +{"status":22, "line":423} +{"auth":"CB", "wait":"CCA", "world":"AAB", "line":424, "disabled":false, "space":"BA", "public":false} +{"state":81, "world":"AC", "subtitle":"CBA", "bad":true, "cleaned":false, "indexed":true, "line":425, "date":"AAB", "coauthors":"BC", "node":"BAC"} +{"wait":"CB", "query":"BCC", "status":97, "line":426} +{"org":47, "query":"CB", "cleaned":true, "line":427, "date":"CC"} +{"org":33, "query":"AC", "status":48, "indexed":false, "line":428, "disabled":true, "abstract":"BC", "space":"ACC"} +{"org":10, "query":"AB", "line":429, "pos":77, "date":"BC"} +{"line":430, "pos":7, "abstract":"CCA", "space":"AA"} +{"bad":false, "user":"CA", "query":"CAB", "line":431, "node":"AC"} +{"auth":"CA", "bad":false, "line":432} +{} +{"auth":"BAA", "org":98, "title":"CCC", "world":"BAC", "line":434, "public":true} +{"state":54, "wait":"AA", "user":"BBA", "indexed":false, "line":435, "disabled":true, "pos":12, "space":"AB"} +{"world":"AC", "title":"CA", "query":"AAA", "line":436, "space":"AB", "coauthors":"AA"} +{"auth":"CB", "wait":"CCC", "bad":false, "line":437, "pos":42, "date":"ABC", "space":"AB", "coauthors":"ABC"} +{"auth":"CBB", "title":"BB", "query":"CB", "line":438, "pos":15, "abstract":"BC", "node":"BBB"} +{"title":"CC", "line":439, "disabled":false} +{"title":"AB", "line":440, "disabled":false} +{"org":3, "bad":true, "user":"BCB", "query":"AB", "indexed":false, "cleaned":true, "line":441, "disabled":false, "space":"BA", "node":"BB"} +{"state":62, "user":"BCC", "status":12, "line":442, "pos":58, "date":"CC", "node":"CB"} +{"world":"BCB", "bad":true, "line":443, "space":"AAB"} +{"state":56, "bad":false, "cleaned":false, "line":444, "disabled":false, "date":"CA", "space":"BBB", "public":true} +{} +{"org":31, "world":"ABC", "cleaned":true, "line":446, "disabled":true, "public":true, "coauthors":"CB"} +{"state":54, "indexed":true, "line":447} +{"state":98, "title":"AC", "wait":"AAC", "world":"BC", "bad":false, "line":448, "disabled":true, "public":true, "node":"ABB"} +{"world":"AAC", "indexed":true, "line":449, "disabled":true, "pos":61} +{"org":56, "title":"CA", "line":450} +{"auth":"BBB", "line":451, "pos":58, "date":"BB", "space":"ABA"} +{"auth":"AB", "world":"CA", "cleaned":true, "line":452} +{"bad":true, "line":453, "disabled":true, "abstract":"AC", "pos":20, "date":"ABB", "node":"CAB"} +{} +{"state":91, "wait":"AC", "org":96, "world":"AA", "subtitle":"BBC", "query":"AA", "cleaned":true, "line":455, "public":false} +{"status":99, "line":456, "disabled":true} +{"org":86, "line":457, "public":true, "coauthors":"AC"} +{"status":14, "cleaned":true, "line":458, "disabled":true} +{"world":"AB", "user":"CB", "query":"AAB", "line":459, "pos":66, "public":false, "node":"BBA"} +{"state":58, "world":"BB", "wait":"CBA", "title":"BCA", "line":460, "pos":95, "abstract":"CCA", "space":"BC", "coauthors":"CB"} +{} +{"auth":"CAC", "title":"AB", "query":"BBA", "user":"CB", "line":462, "abstract":"BCC", "pos":89, "coauthors":"ABB"} +{"org":13, "bad":false, "query":"AA", "status":49, "line":463, "disabled":false} +{"bad":true, "cleaned":false, "line":464, "coauthors":"BB"} +{"org":14, "query":"BA", "line":465, "pos":25, "abstract":"BBA", "space":"AAA", "node":"CAC"} +{"org":63, "title":"CA", "subtitle":"ACC", "query":"BAC", "status":76, "line":466, "abstract":"ACA"} +{"wait":"BA", "subtitle":"BC", "line":467, "disabled":false, "abstract":"AC"} +{"org":76, "title":"CA", "query":"AB", "line":468, "public":false} +{"state":95, "world":"AC", "bad":false, "status":65, "cleaned":false, "line":469, "disabled":false} +{"wait":"AB", "subtitle":"AA", "bad":false, "user":"CC", "query":"BBC", "status":6, "line":470, "date":"CCC"} +{"state":82, "bad":true, "indexed":true, "line":471, "date":"BB", "coauthors":"AAA"} +{} +{"state":12, "auth":"ACB", "world":"CBC", "bad":false, "indexed":true, "line":473, "date":"CA", "space":"ABB", "coauthors":"CC"} +{"subtitle":"AA", "bad":false, "user":"ACC", "line":474, "pos":86, "abstract":"CAC", "space":"BBA"} +{"cleaned":true, "line":475} +{"title":"CC", "wait":"BB", "status":6, "line":476, "abstract":"ACC", "date":"CB", "space":"BA", "public":true} +{"state":96, "wait":"BA", "org":30, "subtitle":"BB", "user":"CBB", "status":19, "line":477} +{"state":78, "org":99, "title":"CC", "line":478, "node":"BAB"} +{"world":"CBC", "bad":false, "line":479, "date":"ACB", "public":true, "node":"CB"} +{"state":0, "query":"ABC", "status":65, "line":480, "disabled":true, "space":"CBA", "node":"BA"} +{"auth":"BAC", "org":24, "subtitle":"BBC", "bad":false, "user":"CAC", "line":481, "date":"BBB", "public":true, "coauthors":"CBA"} +{"org":18, "bad":true, "cleaned":false, "status":3, "indexed":true, "line":482, "date":"BB", "coauthors":"ACC"} +{"wait":"CB", "user":"AC", "line":483, "disabled":false} +{"world":"AC", "subtitle":"AA", "query":"AAB", "line":484, "disabled":true, "space":"CAA"} +{"line":485, "pos":2, "space":"CA"} +{"org":42, "indexed":false, "line":486, "date":"CB"} +{"org":3, "wait":"CAA", "subtitle":"CA", "cleaned":true, "line":487, "disabled":true} +{"org":68, "subtitle":"CCB", "query":"CAA", "cleaned":false, "status":46, "line":488, "pos":87, "public":false, "node":"BC"} +{} +{"status":60, "cleaned":false, "line":490, "space":"CC", "node":"BCB"} +{"state":42, "org":9, "subtitle":"CBA", "user":"BA", "status":96, "line":491, "pos":36} +{"state":16, "title":"BCC", "user":"ABC", "indexed":false, "status":24, "line":492, "disabled":true, "node":"CBC"} +{"auth":"CC", "wait":"BBB", "line":493, "disabled":false, "public":false, "coauthors":"AB"} +{} +{"wait":"BB", "title":"BBC", "subtitle":"BA", "status":3, "cleaned":false, "line":495, "disabled":false, "coauthors":"AB", "node":"BAC"} +{} +{"query":"CC", "indexed":false, "line":497, "coauthors":"CAC", "node":"BC"} +{"auth":"BBA", "state":68, "line":498} +{"state":21, "title":"CCB", "wait":"AAA", "subtitle":"CCC", "user":"BAA", "indexed":true, "line":499, "coauthors":"BB"} +{"auth":"AAA", "subtitle":"CC", "bad":true, "user":"CC", "indexed":true, "line":500, "disabled":true, "date":"AB", "node":"AC"} +{"auth":"BB", "title":"CCA", "user":"BA", "cleaned":true, "line":501, "pos":37, "space":"BA", "public":false} +{"auth":"BCA", "line":502, "date":"BA"} +{"world":"ABA", "bad":true, "indexed":false, "line":503, "disabled":true, "abstract":"AC", "pos":1, "public":false} +{"auth":"BBB", "subtitle":"ACB", "line":504, "space":"AC", "node":"BB"} +{"auth":"CAC", "state":19, "title":"ACA", "wait":"BA", "query":"CC", "line":505} +{"subtitle":"BC", "cleaned":false, "indexed":false, "line":506, "date":"CAB", "public":false, "node":"ABC"} +{"state":87, "wait":"CCC", "query":"CAC", "user":"CBB", "line":507, "abstract":"BBC", "date":"AA", "coauthors":"CA"} +{"auth":"AC", "subtitle":"BC", "bad":false, "query":"ABA", "user":"CBB", "indexed":true, "cleaned":false, "line":508, "coauthors":"BA"} +{"auth":"AA", "title":"ABA", "subtitle":"CCA", "query":"CC", "line":509, "pos":27, "node":"CBB"} +{"org":5, "title":"CAC", "subtitle":"BBB", "line":510, "pos":76, "abstract":"AAB", "space":"AA"} +{"bad":true, "line":511} +{"wait":"ACB", "indexed":false, "line":512} +{"auth":"CBA", "world":"BA", "bad":true, "user":"CBA", "query":"CC", "line":513, "public":false, "coauthors":"CC"} +{} +{"state":97, "wait":"BB", "line":515, "date":"CBC", "space":"CA"} +{"auth":"CBC", "line":516, "disabled":true} +{"state":91, "user":"CCA", "line":517, "coauthors":"BA", "node":"CBA"} +{"bad":false, "cleaned":true, "line":518, "space":"AAB"} +{} +{"title":"CA", "cleaned":false, "status":38, "line":520} +{"auth":"BCA", "world":"AC", "org":71, "user":"CA", "line":521, "abstract":"AAB"} +{"bad":true, "line":522, "pos":28, "abstract":"BAA"} +{"line":523, "coauthors":"CBC", "node":"AAB"} +{"status":51, "cleaned":false, "line":524} +{"query":"AAB", "line":525, "disabled":true, "date":"AA", "public":true, "coauthors":"CA"} +{"org":15, "user":"AC", "cleaned":false, "line":526, "coauthors":"CAC", "node":"BAB"} +{"world":"ABA", "line":527, "disabled":true, "public":true} +{"auth":"BBC", "state":48, "bad":false, "line":528, "abstract":"BB", "date":"BAC", "space":"BA", "public":true} +{"auth":"BA", "wait":"CAC", "subtitle":"ABC", "query":"CB", "indexed":false, "cleaned":false, "line":529, "disabled":true, "date":"CA"} +{"wait":"AC", "world":"ABA", "org":55, "bad":true, "indexed":true, "line":530, "pos":32, "space":"BCA", "public":true} +{"title":"CBC", "wait":"BAA", "line":531} +{"world":"AA", "line":532, "pos":35, "space":"AAB", "public":true} +{"line":533, "space":"AB", "coauthors":"BA"} +{"auth":"CBC", "world":"BB", "line":534, "space":"ACA", "coauthors":"CBB"} +{"wait":"ACA", "status":47, "line":535, "public":true, "node":"BAA"} +{"org":16, "subtitle":"BBB", "line":536, "abstract":"AC", "space":"CB", "coauthors":"CC", "node":"CBC"} +{"wait":"AAB", "line":537, "abstract":"AB", "space":"CAC"} +{"query":"CAC", "line":538} +{"world":"AC", "query":"AAA", "indexed":false, "status":18, "line":539, "pos":62, "space":"BC", "coauthors":"BAC"} +{"org":30, "world":"AA", "query":"BC", "user":"BAC", "status":12, "cleaned":true, "line":540, "space":"AB"} +{"org":30, "user":"CCB", "query":"BB", "cleaned":false, "line":541, "disabled":true, "public":true, "node":"CBA"} +{} +{"subtitle":"ABB", "bad":true, "line":543} +{"subtitle":"BBB", "bad":true, "line":544, "pos":43, "coauthors":"ABB"} +{} +{"subtitle":"AB", "user":"BA", "line":546, "node":"CB"} +{"title":"BBB", "user":"AA", "line":547, "abstract":"CBB", "pos":45} +{"wait":"CCB", "title":"AC", "world":"AAA", "line":548, "abstract":"BBC", "pos":23, "coauthors":"ACC"} +{"org":55, "subtitle":"BA", "line":549, "disabled":true, "date":"CB", "space":"AA"} +{"org":39, "cleaned":true, "line":550, "public":false} +{"state":41, "auth":"CC", "world":"CB", "line":551, "space":"AAB"} +{} +{"state":26, "bad":false, "query":"BAA", "status":84, "indexed":true, "line":553, "disabled":false, "coauthors":"CC", "node":"CBB"} +{"world":"ABA", "user":"CCC", "query":"ABB", "line":554, "space":"ABC", "node":"AAA"} +{"state":18, "wait":"CCB", "bad":true, "user":"BA", "line":555, "space":"CC", "coauthors":"BB", "node":"BBB"} +{"auth":"AA", "state":71, "subtitle":"AA", "query":"ACC", "indexed":true, "line":556, "space":"BAB", "public":false} +{"indexed":true, "cleaned":true, "line":557, "disabled":false, "abstract":"AB"} +{"auth":"BCC", "title":"ACB", "world":"BCA", "user":"BAB", "cleaned":false, "line":558, "space":"BB", "coauthors":"CBC"} +{} +{"auth":"ACC", "org":18, "wait":"AB", "status":1, "indexed":true, "line":560} +{"status":8, "line":561, "abstract":"BA", "public":false} +{"state":27, "title":"ABA", "bad":true, "query":"AAB", "indexed":false, "line":562, "pos":86, "public":true, "coauthors":"BA"} +{} +{"title":"BAC", "wait":"CCC", "user":"BA", "line":564, "disabled":false, "date":"BB", "public":true, "space":"CB", "coauthors":"CCB"} +{"wait":"CAA", "line":565, "pos":80, "space":"AB"} +{"auth":"CBB", "subtitle":"BCA", "user":"CB", "line":566, "abstract":"BC", "date":"AB"} +{"title":"CCB", "status":78, "line":567, "pos":68, "node":"BA"} +{"auth":"BC", "query":"AB", "line":568, "space":"AB", "node":"BB"} +{} +{"line":570, "pos":54} +{"world":"BBB", "user":"CC", "indexed":true, "line":571, "abstract":"CC", "coauthors":"BA", "node":"ABB"} +{"state":41, "line":572} +{"subtitle":"CBC", "cleaned":true, "line":573, "node":"BCB"} +{"title":"ABA", "line":574, "pos":27, "space":"CC"} +{"status":29, "indexed":false, "cleaned":false, "line":575, "pos":52, "public":false, "coauthors":"ACC"} +{"title":"BBB", "org":86, "wait":"AAA", "user":"CC", "query":"CA", "line":576, "disabled":false, "date":"AB", "node":"BC"} +{"line":577, "abstract":"CAA", "date":"BB"} +{"auth":"CCC", "subtitle":"BBB", "query":"ABA", "line":578, "pos":99, "space":"CCB", "public":true, "coauthors":"ACA", "node":"ACB"} +{"wait":"BCC", "line":579} +{"state":99, "world":"BAC", "user":"CA", "line":580} +{"state":55, "world":"AAA", "title":"AAA", "cleaned":false, "line":581, "date":"AC", "public":true, "node":"AA"} +{"query":"ACC", "cleaned":true, "line":582, "disabled":false} +{"auth":"AAB", "query":"BAC", "line":583} +{"auth":"AA", "user":"BAC", "line":584} +{} +{"org":96, "wait":"BC", "bad":false, "cleaned":false, "status":96, "line":586, "pos":95} +{"auth":"BC", "subtitle":"BCB", "bad":true, "user":"BBC", "line":587, "pos":79, "node":"BA"} +{"state":55, "line":588} +{"title":"ABC", "world":"AB", "subtitle":"CBC", "user":"BA", "query":"BAB", "line":589, "date":"AC", "node":"CB"} +{"world":"BAA", "bad":false, "user":"AAB", "cleaned":false, "indexed":false, "line":590} +{"title":"CB", "wait":"BC", "subtitle":"BAC", "cleaned":true, "line":591, "disabled":false, "abstract":"CBB", "public":false, "node":"ACC"} +{"user":"BC", "line":592, "public":false} +{} +{"wait":"CC", "org":57, "title":"BAC", "line":594, "abstract":"AA"} +{"auth":"BBC", "state":3, "world":"AAC", "query":"BA", "line":595, "coauthors":"BB"} +{} +{"subtitle":"CC", "user":"CC", "line":597} +{"wait":"BBA", "user":"AAA", "line":598, "space":"ACB", "node":"AA"} +{"auth":"BB", "user":"ABA", "line":599, "abstract":"AB", "node":"BA"} +{} +{"world":"AAA", "user":"BB", "cleaned":false, "line":601, "space":"AC", "coauthors":"ABB"} +{"title":"CAB", "bad":false, "line":602, "coauthors":"ABB"} +{} +{"world":"CCC", "org":79, "line":604} +{"org":56, "query":"AB", "cleaned":true, "indexed":true, "status":20, "line":605, "public":true, "coauthors":"ACA"} +{"auth":"BBC", "org":13, "subtitle":"CC", "bad":true, "user":"ABC", "line":606, "date":"CA", "public":false} +{"query":"BA", "line":607} +{"bad":true, "line":608, "pos":12, "coauthors":"CB"} +{"bad":false, "status":42, "line":609} +{} +{"bad":true, "line":611} +{"auth":"CCA", "subtitle":"BC", "bad":true, "query":"CAA", "cleaned":false, "line":612, "public":false, "node":"CBA"} +{"org":65, "query":"BC", "line":613} +{} +{"wait":"BAC", "title":"AAB", "user":"CAC", "line":615, "pos":69, "space":"CC", "node":"AAC"} +{"bad":false, "line":616, "abstract":"AB", "pos":65, "coauthors":"BBB"} +{} +{"org":38, "world":"BA", "line":618, "coauthors":"AA", "node":"BC"} +{"cleaned":false, "line":619, "disabled":false} +{"auth":"BC", "line":620, "pos":79, "date":"AB", "coauthors":"BAA", "node":"CB"} +{"auth":"CAA", "title":"CB", "user":"BAC", "cleaned":false, "line":621, "public":false, "space":"CBA"} +{} +{"bad":false, "status":12, "line":623} +{"auth":"BBB", "wait":"BAC", "org":36, "title":"AB", "indexed":false, "cleaned":false, "line":624, "date":"AB", "coauthors":"CB"} +{"wait":"AA", "subtitle":"AB", "query":"CCB", "line":625, "node":"CBB"} +{"wait":"BC", "subtitle":"BA", "bad":true, "user":"AA", "line":626, "pos":3, "date":"BB"} +{"org":28, "user":"BC", "query":"AC", "status":63, "line":627, "pos":45, "public":true, "node":"BC"} +{"query":"BC", "status":47, "line":628, "disabled":false, "date":"CA", "public":false} +{} +{"wait":"CB", "line":630, "pos":67, "coauthors":"AC"} +{"org":33, "world":"BBB", "query":"BB", "status":92, "line":631} +{"state":65, "title":"AC", "world":"CBC", "query":"CBC", "line":632, "date":"CAC", "space":"CC", "coauthors":"CC"} +{} +{"auth":"CC", "query":"BCA", "status":46, "line":634, "disabled":false, "pos":69} +{"wait":"CB", "line":635, "pos":34} +{"state":9, "wait":"CC", "status":23, "line":636, "disabled":true, "date":"BB", "space":"AC"} +{"user":"CCB", "indexed":false, "cleaned":true, "line":637, "pos":65, "date":"AA", "public":true} +{"auth":"BC", "wait":"AB", "title":"BB", "bad":true, "line":638, "abstract":"ACC", "date":"BC", "public":false} +{"state":44, "auth":"BC", "world":"CBC", "line":639, "disabled":false, "date":"CAA"} +{"world":"CB", "title":"ACB", "user":"BA", "query":"AA", "line":640, "disabled":true, "space":"AC"} +{"state":37, "line":641, "disabled":true, "pos":66} +{"world":"AAA", "bad":true, "user":"AAA", "query":"BA", "line":642, "disabled":true, "coauthors":"CBC"} +{"world":"BA", "title":"ABB", "org":96, "bad":false, "query":"AAA", "status":75, "cleaned":false, "line":643, "space":"BA"} +{"state":36, "org":66, "subtitle":"AA", "query":"CA", "cleaned":true, "status":79, "line":644, "date":"CB"} +{"wait":"BC", "line":645, "date":"CBA", "space":"BCB", "public":true, "node":"ABA"} +{"auth":"BB", "org":37, "query":"CAA", "indexed":true, "line":646, "abstract":"CBA", "coauthors":"CBA"} +{} +{} +{"state":58, "world":"BAB", "org":11, "user":"CC", "line":649} +{"title":"CB", "status":19, "line":650, "disabled":false, "public":false, "coauthors":"AA"} +{"user":"BBC", "indexed":true, "line":651, "disabled":true, "pos":8} +{"query":"CC", "cleaned":true, "indexed":false, "line":652, "pos":67, "date":"AA"} +{"auth":"AAC", "line":653, "disabled":true, "public":false, "coauthors":"AAA", "node":"CBB"} +{"bad":true, "query":"AC", "line":654, "disabled":false} +{"world":"CCA", "org":15, "bad":false, "user":"CCC", "line":655, "public":true, "space":"CC"} +{"line":656, "coauthors":"BBB"} +{"title":"BA", "line":657, "date":"ACB"} +{"user":"BC", "query":"CC", "cleaned":true, "line":658, "pos":51, "abstract":"BA"} +{"subtitle":"CCA", "user":"CCA", "cleaned":false, "line":659, "abstract":"BA", "pos":95, "date":"CA"} +{"auth":"CA", "state":23, "org":19, "bad":false, "user":"BCB", "indexed":false, "line":660, "date":"ABA"} +{"state":64, "org":97, "bad":false, "indexed":false, "line":661, "space":"BAB", "coauthors":"BB", "node":"BA"} +{"status":11, "line":662} +{"title":"BCC", "org":44, "subtitle":"ACB", "cleaned":false, "line":663, "pos":58} +{"auth":"ABB", "bad":true, "line":664, "pos":82, "coauthors":"CC", "node":"AB"} +{"bad":false, "cleaned":true, "status":25, "line":665, "disabled":false, "abstract":"BB", "public":true} +{"wait":"AC", "user":"CB", "line":666, "pos":71, "abstract":"ACA", "coauthors":"CBB"} +{"title":"AA", "bad":true, "user":"BB", "line":667, "date":"CA", "space":"BC", "node":"CC"} +{} +{"auth":"AAB", "line":669} +{"wait":"AAC", "query":"ABA", "status":35, "line":670, "disabled":false, "pos":56} +{"org":3, "line":671} +{"state":46, "bad":false, "cleaned":true, "line":672} +{"state":30, "org":9, "status":72, "line":673, "abstract":"ACA", "coauthors":"CB"} +{"auth":"BB", "wait":"CA", "title":"BBB", "bad":true, "user":"AAA", "status":86, "indexed":false, "line":674, "node":"BCC"} +{"indexed":true, "line":675, "pos":63} +{"bad":true, "query":"CBB", "status":5, "line":676, "abstract":"CCC", "public":false, "space":"BB"} +{"title":"BBB", "org":60, "bad":true, "cleaned":false, "line":677, "pos":82, "date":"BAA", "space":"BB", "coauthors":"CAA"} +{} +{} +{"state":73, "bad":false, "cleaned":false, "line":680, "abstract":"CA", "date":"CCA", "space":"CB"} +{"state":92, "query":"CC", "line":681, "abstract":"AB", "date":"BBB", "public":true, "coauthors":"CBA"} +{"subtitle":"CCA", "line":682} +{"world":"BAC", "subtitle":"AC", "line":683, "disabled":true, "abstract":"AA", "pos":55, "space":"AC", "node":"CA"} +{"state":75, "world":"ACA", "query":"BC", "line":684, "coauthors":"AAC"} +{"status":21, "line":685} +{"state":39, "wait":"CB", "title":"CBC", "query":"BB", "cleaned":true, "line":686, "disabled":false} +{"world":"CCA", "wait":"AB", "user":"CC", "query":"BB", "cleaned":true, "line":687} +{"auth":"CAC", "state":94, "wait":"ACC", "title":"BBC", "user":"BB", "line":688, "disabled":false, "pos":16, "coauthors":"AAC"} +{} +{"org":43, "line":690} +{} +{"state":4, "title":"CA", "subtitle":"AA", "query":"BC", "line":692, "pos":57, "date":"BCA", "public":false, "coauthors":"ABB"} +{"wait":"BBA", "line":693} +{"auth":"BCA", "bad":false, "user":"BBA", "line":694, "disabled":false, "date":"CC", "public":true, "coauthors":"CB"} +{"state":66, "wait":"BB", "user":"CC", "indexed":true, "line":695, "pos":99, "space":"BCA"} +{"org":1, "line":696, "disabled":false, "space":"BCC", "coauthors":"BC"} +{"auth":"BC", "cleaned":true, "indexed":false, "line":697, "space":"CBB"} +{"wait":"AC", "indexed":false, "line":698, "pos":44} +{"wait":"AA", "title":"BBB", "org":31, "indexed":true, "line":699, "disabled":false} +{"auth":"BB", "world":"ACC", "bad":true, "indexed":false, "line":700, "abstract":"CB", "pos":5, "space":"ACB", "node":"CC"} +{"cleaned":false, "line":701, "space":"CB"} +{"line":702, "space":"CCC"} +{"world":"CA", "subtitle":"ABA", "line":703, "pos":5, "date":"BA", "coauthors":"AB"} +{} +{"line":705, "date":"BBB"} +{"state":10, "query":"CB", "status":70, "line":706, "abstract":"ABA", "date":"BC"} +{"auth":"CB", "line":707} +{"wait":"BBA", "cleaned":true, "line":708, "pos":94, "date":"CBC"} +{"state":86, "org":5, "world":"BB", "indexed":false, "line":709, "date":"BBB", "space":"CA", "public":true} +{"world":"ACA", "query":"ABC", "status":40, "line":710, "disabled":true, "public":true, "node":"CA"} +{"bad":true, "line":711} +{"query":"AB", "line":712, "coauthors":"BBC", "node":"AA"} +{"user":"ABB", "line":713, "public":false, "space":"AAA", "node":"BBA"} +{"auth":"AC", "wait":"BAC", "bad":true, "line":714, "public":false} +{"line":715, "abstract":"CA", "public":false} +{"user":"AC", "indexed":true, "line":716, "coauthors":"CB"} +{"state":4, "title":"ABB", "org":26, "indexed":true, "line":717, "public":true, "coauthors":"CCA", "node":"AC"} +{"wait":"CA", "title":"CCA", "world":"CCC", "line":718, "abstract":"ACA"} +{"auth":"ACA", "org":29, "subtitle":"AA", "user":"CA", "status":24, "indexed":true, "line":719, "public":false, "node":"CA"} +{} +{"line":721, "disabled":true, "abstract":"BAC"} +{"world":"BC", "line":722} +{"state":27, "auth":"AA", "title":"BC", "world":"CC", "query":"BCC", "line":723, "disabled":true, "pos":9, "public":false, "node":"BCC"} +{"org":78, "wait":"ABA", "cleaned":true, "indexed":true, "line":724, "date":"ACB", "space":"AA"} +{"state":60, "line":725} +{} +{} +{"wait":"ABA", "title":"CAC", "user":"CCC", "line":728} +{"wait":"CC", "indexed":true, "status":39, "line":729, "disabled":true, "public":false} +{"auth":"CB", "subtitle":"BBA", "line":730, "coauthors":"CAC"} +{"world":"CBB", "line":731, "space":"BCB"} +{"cleaned":true, "line":732} +{"org":67, "bad":true, "line":733, "pos":9, "node":"ACC"} +{"world":"BC", "wait":"CAC", "org":58, "subtitle":"ACC", "bad":true, "query":"CAA", "line":734, "abstract":"BCA", "pos":1, "public":true} +{"state":45, "query":"AB", "indexed":false, "line":735, "pos":82, "date":"BC", "public":false, "coauthors":"BA"} +{"state":68, "title":"BC", "cleaned":true, "status":34, "line":736, "disabled":true, "node":"BBB"} +{"auth":"AC", "line":737} +{"line":738, "date":"BA", "space":"CCC", "public":false} +{"line":739, "node":"BAA"} +{"org":72, "title":"BC", "line":740, "pos":51, "coauthors":"CA"} +{"state":72, "user":"CCB", "query":"ACA", "line":741} +{"org":80, "subtitle":"BBA", "bad":true, "user":"BC", "line":742, "pos":52, "coauthors":"BCA"} +{} +{"query":"BC", "line":744, "abstract":"AB", "public":false, "node":"BAC"} +{"world":"CAC", "line":745} +{"auth":"CBB", "title":"AA", "user":"AB", "line":746, "pos":35, "public":false, "space":"AAB"} +{"state":69, "world":"AB", "org":78, "subtitle":"BA", "bad":false, "line":747, "node":"AAA"} +{"bad":true, "line":748, "public":true} +{"wait":"BC", "org":47, "query":"BBB", "line":749} +{"title":"BBB", "line":750} +{"org":33, "query":"CB", "line":751, "disabled":true} +{"subtitle":"BB", "line":752, "space":"CC"} +{"org":89, "line":753} +{"auth":"ABA", "line":754, "coauthors":"ACC"} +{"subtitle":"BA", "line":755, "pos":47} +{"state":81, "subtitle":"CB", "query":"AB", "status":25, "cleaned":false, "line":756, "pos":72, "date":"BA", "coauthors":"BCA"} +{"state":46, "status":88, "line":757, "disabled":false, "public":true} +{"world":"AB", "line":758, "disabled":true, "abstract":"BB", "coauthors":"AAA"} +{"query":"AC", "line":759, "abstract":"AAB"} +{"auth":"BC", "indexed":false, "line":760, "abstract":"BA", "node":"CAA"} +{"state":10, "auth":"BAC", "title":"BC", "query":"BCA", "cleaned":true, "line":761, "disabled":true, "space":"ACC", "coauthors":"ABA"} +{"line":762, "disabled":true, "pos":43} +{"world":"CBA", "user":"BBC", "indexed":true, "line":763} +{"wait":"ACB", "query":"BA", "status":22, "line":764, "pos":70, "abstract":"BAC", "public":false, "space":"BC"} +{} +{"line":766, "disabled":false, "abstract":"CBC", "date":"CA"} +{"title":"CC", "bad":true, "user":"BCC", "indexed":false, "line":767, "date":"BCB", "node":"AAA"} +{"title":"CB", "line":768, "abstract":"AA", "node":"ABB"} +{"org":21, "user":"ABC", "line":769, "abstract":"BB", "date":"CBB", "space":"CC"} +{"auth":"AC", "org":66, "user":"CC", "line":770, "public":false, "space":"CA", "coauthors":"AA"} +{"org":58, "line":771, "coauthors":"BCC", "node":"AC"} +{} +{"auth":"BC", "wait":"CC", "line":773, "abstract":"ACC", "pos":98, "date":"CCC", "space":"ABB", "node":"CB"} +{} +{"query":"BC", "user":"AC", "indexed":true, "line":775, "abstract":"AAA"} +{"subtitle":"BAA", "indexed":false, "line":776} +{"line":777, "pos":33, "date":"CCB", "public":true} +{"world":"BCA", "bad":true, "line":778} +{"auth":"CA", "line":779, "date":"AC", "space":"CAC"} +{"title":"BB", "bad":false, "cleaned":true, "line":780, "disabled":false, "date":"BAB", "space":"ACB"} +{"auth":"CAC", "title":"AAB", "subtitle":"CA", "bad":false, "line":781, "disabled":false, "space":"CB"} +{"state":78, "auth":"AC", "bad":true, "status":46, "line":782, "abstract":"CCA", "pos":97, "public":true} +{"user":"BBA", "line":783} +{} +{"state":63, "title":"CA", "cleaned":true, "line":785, "abstract":"BA", "space":"BCC"} +{"line":786, "node":"CAC"} +{"line":787, "pos":65} +{"line":788, "space":"ABB"} +{} +{"org":14, "line":790, "abstract":"CAB", "coauthors":"BBC"} +{"subtitle":"CBA", "cleaned":false, "line":791, "disabled":false, "pos":57, "node":"CB"} +{"auth":"CAA", "org":84, "wait":"AB", "indexed":true, "status":51, "line":792, "abstract":"CC"} +{"org":72, "bad":true, "line":793, "space":"ACA"} +{} +{"auth":"BC", "state":76, "wait":"CC", "user":"ABB", "cleaned":false, "line":795, "pos":99, "abstract":"CA"} +{"wait":"CCA", "world":"CBC", "line":796, "date":"CB", "public":false} +{"state":49, "line":797, "coauthors":"CC"} +{"wait":"BBB", "title":"ABB", "org":74, "line":798, "disabled":false, "pos":34, "space":"BB"} +{"line":799, "abstract":"CB"} +{"state":84, "user":"ABB", "cleaned":false, "status":18, "line":800, "disabled":true, "date":"CCA", "node":"BA"} +{"state":81, "auth":"CB", "world":"CA", "user":"CAA", "line":801, "date":"AC", "space":"CBC", "coauthors":"BCB"} +{"org":4, "line":802, "disabled":false, "abstract":"ABA", "public":false} +{"auth":"CBC", "state":99, "cleaned":true, "line":803, "disabled":true, "space":"BC", "node":"BBC"} +{"auth":"AC", "wait":"CA", "cleaned":false, "line":804, "pos":54, "date":"BAA", "public":true, "space":"AB"} +{} +{"auth":"BCB", "wait":"BCC", "subtitle":"AAA", "line":806} +{"line":807, "disabled":false, "space":"ACA"} +{"org":96, "query":"CBA", "line":808, "disabled":false, "pos":74, "space":"CA", "public":false} +{} +{"state":12, "title":"AA", "bad":false, "status":20, "line":810, "disabled":true, "coauthors":"CAC", "node":"AB"} +{"auth":"ABC", "line":811, "date":"CA"} +{"title":"AB", "indexed":false, "line":812, "disabled":false, "node":"AAC"} +{} +{"world":"CBA", "status":15, "line":814, "abstract":"CBA"} +{"status":49, "line":815, "pos":49} +{"subtitle":"CAB", "line":816} +{} +{} +{"world":"CAC", "title":"CB", "wait":"AA", "query":"CA", "indexed":true, "line":819, "disabled":true} +{"auth":"ABB", "wait":"AC", "query":"CC", "cleaned":true, "indexed":false, "line":820, "abstract":"AA", "public":false, "node":"AB"} +{"org":5, "wait":"BA", "indexed":true, "line":821, "node":"AB"} +{"title":"CC", "wait":"CC", "bad":false, "query":"BCC", "indexed":false, "line":822, "pos":27, "date":"CB", "node":"CBA"} +{"query":"BC", "status":28, "line":823, "public":false} +{"status":1, "line":824, "abstract":"BB"} +{} +{"auth":"AA", "title":"BC", "query":"CA", "status":33, "line":826} +{"state":9, "title":"BB", "subtitle":"ACC", "bad":true, "query":"BA", "status":41, "line":827, "abstract":"ACB", "public":false} +{"auth":"AB", "subtitle":"CAB", "line":828, "public":false, "coauthors":"AB", "node":"BAC"} +{"line":829, "disabled":false, "public":true, "node":"CBC"} +{"auth":"BAB", "line":830} +{"wait":"BBA", "bad":true, "indexed":false, "line":831, "space":"BB"} +{"org":70, "wait":"BC", "world":"AC", "indexed":true, "status":96, "line":832, "disabled":true, "space":"AB"} +{"state":8, "world":"BAB", "bad":true, "indexed":true, "status":18, "line":833, "date":"BA", "space":"BA"} +{"query":"AB", "line":834} +{"bad":true, "status":5, "line":835} +{"world":"BAC", "subtitle":"BB", "bad":false, "user":"AB", "indexed":false, "cleaned":true, "line":836} +{"line":837, "public":false} +{"line":838, "pos":7} +{"auth":"CA", "query":"ABB", "indexed":true, "line":839, "public":true} +{"wait":"CC", "bad":false, "line":840, "date":"AAB", "public":false, "coauthors":"BCB"} +{"auth":"AB", "state":97, "org":24, "line":841, "pos":41, "node":"BC"} +{"wait":"BB", "world":"CBA", "user":"BAA", "status":18, "line":842, "date":"BAB", "public":true} +{"title":"BA", "subtitle":"CA", "query":"CCB", "line":843, "space":"BB"} +{"auth":"BB", "world":"ACA", "line":844, "pos":29} +{"state":65, "org":40, "query":"CAA", "user":"AB", "indexed":false, "cleaned":false, "line":845} +{"title":"CB", "cleaned":false, "indexed":false, "line":846} +{"wait":"CAA", "indexed":false, "line":847, "disabled":false} +{} +{"title":"CB", "query":"CC", "line":849, "abstract":"CB", "pos":10, "public":true} +{"auth":"CB", "subtitle":"BA", "cleaned":true, "line":850, "disabled":true, "pos":84} +{"org":45, "wait":"BA", "query":"CCC", "line":851, "date":"BCA", "coauthors":"CAB"} +{"state":84, "title":"CB", "line":852, "pos":71, "space":"CA"} +{"line":853, "public":true, "node":"AA"} +{"subtitle":"BC", "user":"AB", "cleaned":true, "line":854, "public":true} +{"state":26, "wait":"BA", "world":"BBB", "user":"BB", "status":53, "line":855, "abstract":"ABA", "pos":72, "space":"AC"} +{"line":856, "public":false, "coauthors":"CA"} +{"wait":"ABB", "subtitle":"CBA", "line":857, "pos":44} +{"auth":"ABA", "wait":"CC", "org":0, "bad":true, "query":"BB", "line":858, "disabled":false, "public":true} +{"bad":false, "user":"AA", "line":859, "pos":90} +{"world":"BCC", "title":"BAA", "bad":false, "user":"CAA", "query":"BBC", "line":860, "date":"CAA", "space":"BCB", "public":true, "coauthors":"CB"} +{"title":"BAB", "world":"BC", "subtitle":"AA", "cleaned":false, "status":9, "line":861, "pos":95} +{"title":"AC", "line":862} +{"wait":"CB", "bad":false, "status":89, "line":863, "coauthors":"AB"} +{"subtitle":"ACC", "indexed":true, "cleaned":true, "line":864} +{"title":"AB", "subtitle":"CBB", "query":"ACA", "indexed":true, "line":865, "disabled":true} +{"title":"BC", "world":"BB", "query":"AA", "user":"ACB", "status":43, "cleaned":false, "line":866, "coauthors":"CBC", "node":"ACB"} +{} +{"org":25, "wait":"AC", "indexed":false, "line":868, "disabled":false, "abstract":"CA", "pos":48} +{"bad":true, "status":34, "line":869, "pos":32, "date":"AC", "public":true, "node":"AA"} +{"state":33, "wait":"AAC", "indexed":true, "status":20, "line":870, "abstract":"BA"} +{"wait":"CCC", "subtitle":"AC", "line":871, "disabled":false, "space":"BA", "public":true, "coauthors":"BCC"} +{"status":49, "line":872} +{"state":90, "title":"ACC", "world":"CBB", "subtitle":"BAB", "bad":false, "status":94, "line":873, "abstract":"CB"} +{"title":"BCB", "line":874} +{"cleaned":false, "line":875} +{"wait":"BAA", "subtitle":"BBC", "line":876} +{"auth":"AB", "org":35, "bad":false, "indexed":false, "line":877, "coauthors":"BBA"} +{"line":878, "public":false} +{} +{"auth":"CB", "wait":"CBC", "indexed":false, "line":880, "public":true} +{"query":"CC", "status":4, "line":881, "disabled":true, "node":"CA"} +{"title":"BB", "line":882} +{"state":53, "bad":false, "cleaned":false, "status":63, "line":883, "coauthors":"BAA"} +{"auth":"ACA", "world":"AC", "user":"CBC", "line":884, "date":"BCA", "node":"BBC"} +{"auth":"BAB", "state":11, "world":"CB", "org":77, "query":"BA", "cleaned":false, "line":885, "space":"AC"} +{"world":"CA", "user":"CA", "line":886, "node":"CB"} +{"state":32, "org":50, "wait":"AA", "line":887, "disabled":false, "space":"BBA", "public":false} +{"cleaned":true, "line":888, "pos":70, "node":"ABC"} +{"org":63, "user":"AAB", "query":"BB", "line":889, "date":"BC", "space":"CBB", "node":"ABC"} +{"wait":"BB", "user":"BB", "query":"CB", "line":890, "space":"BB", "coauthors":"BA", "node":"ABC"} +{"auth":"BC", "world":"CC", "subtitle":"CB", "line":891, "public":false, "coauthors":"BC"} +{"state":13, "org":38, "line":892, "coauthors":"BC", "node":"ABC"} +{"auth":"CC", "world":"CAC", "line":893, "date":"BBA", "node":"CBC"} +{} +{"auth":"AA", "line":895, "coauthors":"BB"} +{"auth":"AA", "state":76, "status":85, "line":896, "date":"CCC", "public":true, "coauthors":"AB"} +{"auth":"AB", "indexed":true, "cleaned":false, "line":897} +{"line":898, "coauthors":"CBB"} +{} +{"wait":"ACC", "line":900, "abstract":"BBA"} +{"auth":"AA", "wait":"BCB", "cleaned":false, "line":901, "abstract":"AAC"} +{"state":68, "title":"AC", "subtitle":"BB", "line":902} +{"state":41, "wait":"ABA", "bad":false, "user":"BBA", "status":46, "line":903, "node":"AAB"} +{} +{"cleaned":false, "line":905, "pos":33} +{"bad":false, "query":"BA", "line":906, "pos":48, "space":"CB", "public":true} +{"query":"CB", "indexed":true, "line":907, "pos":41, "abstract":"CBB", "space":"BA", "public":false, "node":"BC"} +{"title":"AB", "line":908} +{"auth":"BC", "title":"CB", "line":909, "disabled":false, "space":"CA", "public":true, "coauthors":"BC"} +{"world":"AA", "user":"ABA", "indexed":false, "line":910, "abstract":"CC"} +{"auth":"CCA", "indexed":false, "line":911, "date":"AC", "public":false} +{"world":"AAB", "bad":true, "line":912} +{"subtitle":"CBB", "line":913, "public":true} +{"wait":"CB", "line":914, "disabled":false, "pos":71, "date":"BA", "space":"CBA", "public":false, "coauthors":"BB"} +{"org":67, "wait":"CA", "bad":false, "line":915, "disabled":false, "public":true} +{} +{"line":917, "date":"CB"} +{"auth":"CBB", "world":"AAA", "status":83, "indexed":false, "line":918, "disabled":true, "date":"CBA", "coauthors":"ACC"} +{"wait":"AB", "title":"BA", "status":33, "line":919, "disabled":false} +{"wait":"ACB", "cleaned":false, "line":920, "abstract":"AA", "coauthors":"BCB"} +{"wait":"ABB", "org":40, "world":"BC", "subtitle":"CA", "user":"AAC", "status":14, "indexed":true, "line":921, "pos":66} +{"auth":"BA", "org":22, "wait":"BAB", "bad":true, "user":"ACC", "status":32, "line":922} +{"world":"BA", "query":"CAB", "status":0, "line":923} +{"org":84, "bad":true, "line":924, "coauthors":"BAB"} +{"auth":"ACC", "subtitle":"AAA", "query":"CCA", "cleaned":false, "line":925, "pos":60, "space":"BC"} +{"wait":"BC", "subtitle":"CCC", "bad":false, "cleaned":false, "indexed":false, "line":926, "public":false, "coauthors":"AC"} +{"auth":"CA", "state":91, "org":6, "world":"AA", "wait":"ABB", "query":"AAC", "line":927, "date":"CA", "node":"BAC"} +{"world":"BCA", "query":"AA", "user":"BBC", "line":928, "disabled":false} +{"world":"CBC", "user":"CBC", "line":929, "date":"CAC"} +{"world":"BCB", "bad":false, "user":"BB", "line":930} +{"auth":"CA", "world":"AA", "query":"ABA", "user":"AA", "indexed":true, "line":931, "coauthors":"BBC"} +{"auth":"BAA", "bad":true, "line":932, "disabled":false, "pos":93, "abstract":"CCA", "date":"BBA", "coauthors":"AA"} +{"line":933, "space":"CAB", "node":"AB"} +{} +{"status":60, "cleaned":true, "indexed":true, "line":935, "pos":35, "space":"CAB", "node":"BBB"} +{"wait":"AAA", "bad":true, "status":26, "line":936, "abstract":"ACB", "space":"BBA", "coauthors":"CCC"} +{"title":"BA", "bad":true, "line":937, "date":"BBA", "public":true} +{} +{"query":"BA", "user":"BA", "line":939, "disabled":true} +{"line":940, "date":"CC"} +{"wait":"CAB", "query":"BCA", "user":"BC", "line":941, "pos":8, "coauthors":"ACC"} +{"line":942, "space":"BA"} +{"auth":"CA", "org":11, "line":943, "pos":99} +{"org":83, "line":944, "disabled":false, "date":"BBA", "space":"AC", "node":"AC"} +{"world":"CCA", "line":945, "node":"BC"} +{"org":95, "title":"CA", "world":"BA", "line":946, "pos":36, "coauthors":"CA"} +{"state":93, "line":947} +{"cleaned":false, "status":5, "line":948, "abstract":"BB", "public":false, "coauthors":"ABC"} +{"world":"CA", "org":61, "bad":false, "query":"CC", "cleaned":true, "line":949, "pos":14, "space":"CC"} +{"state":91, "line":950, "abstract":"BA", "date":"AB"} +{"auth":"BBC", "line":951, "date":"BB"} +{"auth":"BAB", "line":952, "disabled":true, "node":"AAA"} +{"auth":"CAA", "subtitle":"ABA", "bad":true, "line":953} +{"auth":"CA", "wait":"BB", "org":12, "user":"BCC", "cleaned":false, "line":954, "public":false, "coauthors":"AA"} +{"org":93, "cleaned":true, "line":955, "disabled":true, "public":true, "node":"ACA"} +{"line":956, "pos":10} +{"org":74, "world":"CCC", "subtitle":"AB", "user":"AAA", "cleaned":true, "line":957, "pos":70, "public":true, "node":"CC"} +{"state":51, "line":958} +{"world":"CCA", "title":"BCB", "user":"AB", "indexed":true, "line":959, "disabled":true, "pos":21, "date":"CBC"} +{"org":86, "wait":"BC", "query":"BB", "user":"AA", "indexed":true, "line":960, "pos":58, "date":"AB"} +{"line":961, "node":"CC"} +{"auth":"BCB", "world":"ACC", "subtitle":"CA", "bad":true, "user":"BA", "indexed":false, "line":962, "public":false} +{} +{"status":37, "line":964} +{"state":70, "status":76, "indexed":false, "line":965, "disabled":true, "space":"BB"} +{} +{"state":67, "world":"CA", "title":"AA", "line":967, "abstract":"BA", "space":"BAA"} +{"auth":"CA", "world":"AA", "bad":true, "query":"BC", "status":53, "indexed":false, "line":968, "date":"AB", "node":"BAA"} +{"query":"AC", "cleaned":true, "line":969, "abstract":"BC", "space":"CAB", "coauthors":"BAA"} +{"wait":"BCA", "world":"CB", "title":"BC", "indexed":false, "line":970, "disabled":true, "pos":70, "date":"AB"} +{} +{"subtitle":"BC", "query":"AA", "line":972} +{"line":973, "public":true} +{"org":75, "world":"AAB", "subtitle":"BB", "user":"CC", "line":974, "space":"CA"} +{"auth":"BCB", "cleaned":true, "line":975} +{"title":"BAC", "user":"CB", "line":976, "public":false} +{"subtitle":"BAC", "indexed":false, "cleaned":false, "line":977, "disabled":false, "abstract":"ABC", "space":"ABA"} +{"state":63, "bad":false, "line":978, "pos":93, "node":"AAC"} +{} +{"cleaned":false, "line":980, "abstract":"CCB"} +{"state":40, "title":"ABA", "subtitle":"CAB", "query":"BC", "line":981, "date":"CA", "coauthors":"AB"} +{} +{"auth":"ABA", "subtitle":"ACC", "user":"AA", "query":"AC", "cleaned":true, "line":983, "date":"ACB", "node":"CB"} +{"state":32, "title":"ABC", "org":58, "status":95, "line":984, "disabled":true, "pos":6, "space":"CBB"} +{"title":"BCC", "subtitle":"CCC", "user":"BBC", "line":985, "public":false, "coauthors":"CCB", "node":"AA"} +{"subtitle":"ACA", "query":"BCC", "status":43, "cleaned":true, "indexed":true, "line":986, "abstract":"CAC"} +{} +{"world":"CAB", "org":21, "indexed":true, "line":988, "abstract":"ABC"} +{"title":"CBC", "status":66, "line":989} +{} +{"array":[5]} +{"array":["foo", "bar", "baz"]} +{"array":["bar", "baz", "foo"]} +{"array":["bar", "baz"]} +{"array":["baz", "foo"]} +{"line":991, "abstract":"BA", "node":"BBB"} +{"line":992, "disabled":true, "pos":29, "public":false} +{"state":53, "wait":"CB", "subtitle":"CCC", "line":993, "date":"CAC", "public":false, "coauthors":"BB"} +{"wait":"CBA", "title":"CA", "subtitle":"BB", "user":"BAA", "line":994, "disabled":true, "date":"BB", "coauthors":"CCC", "node":"CC"} +{"title":"BB", "user":"AA", "query":"CAA", "status":43, "line":995, "pos":6, "abstract":"CC", "public":true} +{"wait":"AC", "query":"BA", "line":996, "coauthors":"BB", "node":"CCC"} +{"auth":"BC", "title":"CAC", "subtitle":"BA", "line":997, "date":"BAA"} +{"wait":"AB", "user":"ABC", "line":998, "pos":41, "node":"CAC"} +{"state":4, "title":"AC", "bad":true, "status":59, "line":999, "disabled":true} +{"user":"BC", "line":1000} +{"wait":null, "line":1000} +{"age":25} +{"age":25.0} +{} diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 04b969ae10..9f086763c2 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -464,8 +464,8 @@ CREATE TEMP TABLE test_json ( ); INSERT INTO test_json VALUES ('scalar','"a scalar"'), -('array','["zero", "one","two",null,"four","five"]'), -('object','{"field1":"val1","field2":"val2","field3":null}'); +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); SELECT test_json -> 'x' FROM test_json WHERE json_type = 'scalar'; @@ -522,6 +522,36 @@ WHERE json_type = 'array'; two (1 row) +SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array'; + ?column? +---------- + [1,2,3] +(1 row) + +SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array'; + ?column? +---------- + {"f1":9} +(1 row) + +SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + 4 +(1 row) + +SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + [1,2,3] +(1 row) + +SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + {"f1":9} +(1 row) + SELECT json_object_keys(test_json) FROM test_json WHERE json_type = 'scalar'; @@ -538,7 +568,20 @@ WHERE json_type = 'object'; field1 field2 field3 -(3 rows) + field4 + field5 + field6 +(6 rows) + +-- test extending object_keys resultset - initial resultset size is 256 +select count(*) from + (select json_object_keys(json_object(array_agg(g))) + from (select unnest(array['f'||n,n::text])as g + from generate_series(1,300) as n) x ) y; + count +------- + 300 +(1 row) -- nulls select (test_json->'field3') is null as expect_false diff --git a/src/test/regress/expected/json_1.out b/src/test/regress/expected/json_1.out index 07b25ca96c..13f7687608 100644 --- a/src/test/regress/expected/json_1.out +++ b/src/test/regress/expected/json_1.out @@ -464,8 +464,8 @@ CREATE TEMP TABLE test_json ( ); INSERT INTO test_json VALUES ('scalar','"a scalar"'), -('array','["zero", "one","two",null,"four","five"]'), -('object','{"field1":"val1","field2":"val2","field3":null}'); +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); SELECT test_json -> 'x' FROM test_json WHERE json_type = 'scalar'; @@ -522,6 +522,36 @@ WHERE json_type = 'array'; two (1 row) +SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array'; + ?column? +---------- + [1,2,3] +(1 row) + +SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array'; + ?column? +---------- + {"f1":9} +(1 row) + +SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + 4 +(1 row) + +SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + [1,2,3] +(1 row) + +SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object'; + ?column? +---------- + {"f1":9} +(1 row) + SELECT json_object_keys(test_json) FROM test_json WHERE json_type = 'scalar'; @@ -538,7 +568,20 @@ WHERE json_type = 'object'; field1 field2 field3 -(3 rows) + field4 + field5 + field6 +(6 rows) + +-- test extending object_keys resultset - initial resultset size is 256 +select count(*) from + (select json_object_keys(json_object(array_agg(g))) + from (select unnest(array['f'||n,n::text])as g + from generate_series(1,300) as n) x ) y; + count +------- + 300 +(1 row) -- nulls select (test_json->'field3') is null as expect_false diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out new file mode 100644 index 0000000000..e25483d2f5 --- /dev/null +++ b/src/test/regress/expected/jsonb.out @@ -0,0 +1,2056 @@ +-- Strings. +SELECT '""'::jsonb; -- OK. + jsonb +------- + "" +(1 row) + +SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed +ERROR: invalid input syntax for type json +LINE 1: SELECT $$''$$::jsonb; + ^ +DETAIL: Token "'" is invalid. +CONTEXT: JSON data, line 1: '... +SELECT '"abc"'::jsonb; -- OK + jsonb +------- + "abc" +(1 row) + +SELECT '"abc'::jsonb; -- ERROR, quotes not closed +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc'::jsonb; + ^ +DETAIL: Token ""abc" is invalid. +CONTEXT: JSON data, line 1: "abc +SELECT '"abc +def"'::jsonb; -- ERROR, unescaped newline in string constant +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc + ^ +DETAIL: Character with value 0x0a must be escaped. +CONTEXT: JSON data, line 1: "abc +SELECT '"\n\"\\"'::jsonb; -- OK, legal escapes + jsonb +---------- + "\n\"\\" +(1 row) + +SELECT '"\v"'::jsonb; -- ERROR, not a valid JSON escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\v"'::jsonb; + ^ +DETAIL: Escape sequence "\v" is invalid. +CONTEXT: JSON data, line 1: "\v... +SELECT '"\u"'::jsonb; -- ERROR, incomplete escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u" +SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u00"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u00" +SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u000g"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u000g... +SELECT '"\u0000"'::jsonb; -- OK, legal escape + jsonb +----------- + "\\u0000" +(1 row) + +-- use octet_length here so we don't get an odd unicode char in the +-- output +SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK + octet_length +-------------- + 5 +(1 row) + +-- Numbers. +SELECT '1'::jsonb; -- OK + jsonb +------- + 1 +(1 row) + +SELECT '0'::jsonb; -- OK + jsonb +------- + 0 +(1 row) + +SELECT '01'::jsonb; -- ERROR, not valid according to JSON spec +ERROR: invalid input syntax for type json +LINE 1: SELECT '01'::jsonb; + ^ +DETAIL: Token "01" is invalid. +CONTEXT: JSON data, line 1: 01 +SELECT '0.1'::jsonb; -- OK + jsonb +------- + 0.1 +(1 row) + +SELECT '9223372036854775808'::jsonb; -- OK, even though it's too large for int8 + jsonb +--------------------- + 9223372036854775808 +(1 row) + +SELECT '1e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1.3e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1f2'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1f2'::jsonb; + ^ +DETAIL: Token "1f2" is invalid. +CONTEXT: JSON data, line 1: 1f2 +SELECT '0.x1'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '0.x1'::jsonb; + ^ +DETAIL: Token "0.x1" is invalid. +CONTEXT: JSON data, line 1: 0.x1 +SELECT '1.3ex100'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1.3ex100'::jsonb; + ^ +DETAIL: Token "1.3ex100" is invalid. +CONTEXT: JSON data, line 1: 1.3ex100 +-- Arrays. +SELECT '[]'::jsonb; -- OK + jsonb +------- + [] +(1 row) + +SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb; -- OK + jsonb +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +(1 row) + +SELECT '[1,2]'::jsonb; -- OK + jsonb +-------- + [1, 2] +(1 row) + +SELECT '[1,2,]'::jsonb; -- ERROR, trailing comma +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2,]'::jsonb; + ^ +DETAIL: Expected JSON value, but found "]". +CONTEXT: JSON data, line 1: [1,2,] +SELECT '[1,2'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,2 +SELECT '[1,[2]'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,[2]'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,[2] +-- Objects. +SELECT '{}'::jsonb; -- OK + jsonb +------- + {} +(1 row) + +SELECT '{"abc"}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"}'::jsonb; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: JSON data, line 1: {"abc"} +SELECT '{"abc":1}'::jsonb; -- OK + jsonb +------------ + {"abc": 1} +(1 row) + +SELECT '{1:"abc"}'::jsonb; -- ERROR, keys must be strings +ERROR: invalid input syntax for type json +LINE 1: SELECT '{1:"abc"}'::jsonb; + ^ +DETAIL: Expected string or "}", but found "1". +CONTEXT: JSON data, line 1: {1... +SELECT '{"abc",1}'::jsonb; -- ERROR, wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc",1}'::jsonb; + ^ +DETAIL: Expected ":", but found ",". +CONTEXT: JSON data, line 1: {"abc",... +SELECT '{"abc"=1}'::jsonb; -- ERROR, totally wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"=1}'::jsonb; + ^ +DETAIL: Token "=" is invalid. +CONTEXT: JSON data, line 1: {"abc"=... +SELECT '{"abc"::1}'::jsonb; -- ERROR, another wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"::1}'::jsonb; + ^ +DETAIL: Expected JSON value, but found ":". +CONTEXT: JSON data, line 1: {"abc"::... +SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK + jsonb +-------------------------------------------------------------------- + {"abc": 1, "def": 2, "ghi": [3, 4], "hij": {"klm": 5, "nop": [6]}} +(1 row) + +SELECT '{"abc":1:2}'::jsonb; -- ERROR, colon in wrong spot +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1:2}'::jsonb; + ^ +DETAIL: Expected "," or "}", but found ":". +CONTEXT: JSON data, line 1: {"abc":1:... +SELECT '{"abc":1,3}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1,3}'::jsonb; + ^ +DETAIL: Expected string, but found "3". +CONTEXT: JSON data, line 1: {"abc":1,3... +-- Miscellaneous stuff. +SELECT 'true'::jsonb; -- OK + jsonb +------- + true +(1 row) + +SELECT 'false'::jsonb; -- OK + jsonb +------- + false +(1 row) + +SELECT 'null'::jsonb; -- OK + jsonb +------- + null +(1 row) + +SELECT ' true '::jsonb; -- OK, even with extra whitespace + jsonb +------- + true +(1 row) + +SELECT 'true false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true false'::jsonb; + ^ +DETAIL: Expected end of input, but found "false". +CONTEXT: JSON data, line 1: true false +SELECT 'true, false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true, false'::jsonb; + ^ +DETAIL: Expected end of input, but found ",". +CONTEXT: JSON data, line 1: true,... +SELECT 'truf'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'truf'::jsonb; + ^ +DETAIL: Token "truf" is invalid. +CONTEXT: JSON data, line 1: truf +SELECT 'trues'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'trues'::jsonb; + ^ +DETAIL: Token "trues" is invalid. +CONTEXT: JSON data, line 1: trues +SELECT ''::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ''::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT ' '::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ' '::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +-- make sure jsonb is passed through json generators without being escaped +SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); + array_to_json +-------------------------- + [{"a": 1},{"b": [2, 3]}] +(1 row) + +-- jsonb extraction functions +CREATE TEMP TABLE test_jsonb ( + json_type text, + test_json jsonb +); +INSERT INTO test_jsonb VALUES +('scalar','"a scalar"'), +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_field (jsonb -> text operator) on a scalar +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_field (jsonb -> text operator) on an array +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + "val2" +(1 row) + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_field_text (jsonb ->> text operator) on an array +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + val2 +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on a scalar +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + "two" +(1 row) + +SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object'; +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on an object +SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + 4 +(1 row) + +SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_array_element_text on a scalar +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + two +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object'; +ERROR: cannot call jsonb_array_element_text on an object +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_keys on a scalar +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_keys on an array +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; + jsonb_object_keys +------------------- + field1 + field2 + field3 + field4 + field5 + field6 +(6 rows) + +-- nulls +SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object'; + expect_true +------------- + t +(1 row) + +SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; + expect_true +------------- + t +(1 row) + +-- equality and inequality +SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; + ?column? +---------- + t +(1 row) + +CREATE TABLE testjsonb (j jsonb); +\copy testjsonb FROM 'data/jsonb.data' +-- containment +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + ?column? +---------- + f +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; + ?column? +---------- + t +(1 row) + +SELECT '5'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +SELECT '[5]'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; + ?column? +---------- + f +(1 row) + +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; + ?column? +---------- + t +(1 row) + +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; + ?column? +---------- + f +(1 row) + +-- array length +SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); + jsonb_array_length +-------------------- + 5 +(1 row) + +SELECT jsonb_array_length('[]'); + jsonb_array_length +-------------------- + 0 +(1 row) + +SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}'); +ERROR: cannot get array length of a non-array +SELECT jsonb_array_length('4'); +ERROR: cannot get array length of a scalar +-- each +SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}'); + jsonb_each +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,null) +(3 rows) + +SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,"""first""") + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,"""cc""") + (n,null) +(5 rows) + +SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | null + f5 | 99 + f6 | "stringy" +(5 rows) + +SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | "first" + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | "cc" + n | null +(5 rows) + +SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}'); + jsonb_each_text +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,) + (f5,null) +(4 rows) + +SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,first) + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,cc) + (n,) +(5 rows) + +SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | + f5 | 99 + f6 | stringy +(5 rows) + +SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | first + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | cc + n | +(5 rows) + +-- exists +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); + jsonb_exists +-------------- + f +(1 row) + +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +-- array exists - array elements should behave as keys +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + ?column? +---------- + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + ?column? +---------- + t +(1 row) + +-- typeof +SELECT jsonb_typeof('{}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('[]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('["a", 1]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('null') AS "null"; + null +------ + null +(1 row) + +SELECT jsonb_typeof('1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1e2') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('true') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('false') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('"hello"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"true"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"1.0"') AS string; + string +-------- + string +(1 row) + +-- extract_path, extract_path_as_text +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path +-------------------- + "stringy" +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path +-------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path +-------------------- + "f3" +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path +-------------------- + 1 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path_text +------------------------- + stringy +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path_text +------------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path_text +------------------------- + f3 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path_text +------------------------- + 1 +(1 row) + +-- extract_path nulls +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +-- extract_path operators +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; + ?column? +----------- + "stringy" +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; + ?column? +---------- + "f3" +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; + ?column? +---------- + stringy +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; + ?column? +---------- + f3 +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +-- same using array literals +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}'; + ?column? +----------- + "stringy" +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}'; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}'; + ?column? +---------- + "f3" +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}'; + ?column? +---------- + stringy +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}'; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}'; + ?column? +---------- + f3 +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}'; + ?column? +---------- + 1 +(1 row) + +-- same on jsonb scalars (expecting errors) +SELECT '42'::jsonb#>array['f2']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>array['0']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>>array['f2']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>>array['0']; +ERROR: cannot call extract path from a scalar +-- array_elements +SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); + jsonb_array_elements +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]'); + jsonb_array_elements_text +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +-- populate_record +CREATE TYPE jbpop AS (a text, b int, c timestamp); +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; + a | b | c +-------------------+---+--- + [100, 200, false] | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; + a | b | c +-------------------+---+-------------------------- + [100, 200, false] | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q; +ERROR: invalid input syntax for type timestamp: "[100, 200, false]" +-- populate_recordset +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +-----------------+----+-------------------------- + [100, 200, 300] | 99 | + {"z": true} | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; +ERROR: invalid input syntax for type timestamp: "[100, 200, 300]" +-- using the default use_json_as_text argument +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +ERROR: cannot populate with a nested object unless use_json_as_text is true +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +ERROR: cannot populate with a nested object unless use_json_as_text is true +-- handling of unicode surrogate pairs +SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8; + correct_in_utf8 +----------------- + 10 +(1 row) + +SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; + ^ +DETAIL: Unicode high surrogate must not follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +-- handling of simple unicode escapes +SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8; + correct_in_utf8 +---------------------- + the Copyright © sign +(1 row) + +SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE; + correct_everywhere +-------------------- + dollar $ character +(1 row) + +SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' AS not_unescaped; + not_unescaped +-------------------- + null \u0000 escape +(1 row) + +-- indexing +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +CREATE INDEX jidx ON testjsonb USING gin (j); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}'; + count +------- + 3 +(1 row) + +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + count +------- + 1009 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +-- array exists - array elements should behave as keys (for GIN index scans too) +CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +RESET enable_seqscan; +SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; + count +------- + 4788 +(1 row) + +SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key; + key | count +-----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + abstract | 161 + array | 5 + age | 2 +(24 rows) + +-- sort/hash +SELECT count(distinct j) FROM testjsonb; + count +------- + 891 +(1 row) + +SET enable_hashagg = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 891 +(1 row) + +SET enable_hashagg = on; +SET enable_sort = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 891 +(1 row) + +SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); + j +---- + {} +(1 row) + +SET enable_sort = on; +RESET enable_hashagg; +RESET enable_sort; +DROP INDEX jidx; +DROP INDEX jidx_array; +-- btree +CREATE INDEX jidx ON testjsonb USING btree (j); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j > '{"p":1}'; + count +------- + 884 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}'; + count +------- + 1 +(1 row) + +--gin hash +DROP INDEX jidx; +CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + count +------- + 1009 +(1 row) + +RESET enable_seqscan; +DROP INDEX jidx; +-- nested tests +SELECT '{"ff":{"a":12,"b":16}}'::jsonb; + jsonb +---------------------------- + {"ff": {"a": 12, "b": 16}} +(1 row) + +SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb; + jsonb +--------------------------------------- + {"ff": {"a": 12, "b": 16}, "qq": 123} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb; + jsonb +-------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": 12, "b": 16, "c": ["c1", "c2"], "d": {"d1": "d3", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------ + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2"], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2", ["c3"], {"c4": 4}], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"ff":["a","aaa"]}'::jsonb; + jsonb +---------------------- + {"ff": ["a", "aaa"]} +(1 row) + +SELECT + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff', + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq', + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f, + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t, + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; + ?column? | ?column? | f | t | ?column? +--------------------+----------+---+---+---------- + {"a": 12, "b": 16} | 123 | f | t | [1, 2] +(1 row) + +-- nested containment +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; + ?column? +---------- + t +(1 row) + +-- nested object field / array index lookup +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n'; + ?column? +---------- + null +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b'; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c'; + ?column? +---------- + {"1": 2} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd'; + ?column? +--------------- + {"1": [2, 3]} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1'; + ?column? +---------- + [2, 3] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e'; + ?column? +---------- + +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on an object +SELECT '["a","b","c",[1,2],null]'::jsonb -> 0; + ?column? +---------- + "a" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 1; + ?column? +---------- + "b" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 2; + ?column? +---------- + "c" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1; + ?column? +---------- + 2 +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 4; + ?column? +---------- + null +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 5; + ?column? +---------- + +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> -1; + ?column? +---------- + +(1 row) + +--nested path extraction +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; + ?column? +---------- + "b" +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; + ?column? +---------- + 2 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; + ?column? +---------- + 3 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; + ?column? +---------- + +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; + ?column? +---------- + 0 +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; + ?column? +---------- + [3, 4] +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; + ?column? +--------------- + {"5": "five"} +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; + ?column? +---------- + "five" +(1 row) + +--nested exists +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; + ?column? +---------- + f +(1 row) + diff --git a/src/test/regress/expected/jsonb_1.out b/src/test/regress/expected/jsonb_1.out new file mode 100644 index 0000000000..06af5c8252 --- /dev/null +++ b/src/test/regress/expected/jsonb_1.out @@ -0,0 +1,2056 @@ +-- Strings. +SELECT '""'::jsonb; -- OK. + jsonb +------- + "" +(1 row) + +SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed +ERROR: invalid input syntax for type json +LINE 1: SELECT $$''$$::jsonb; + ^ +DETAIL: Token "'" is invalid. +CONTEXT: JSON data, line 1: '... +SELECT '"abc"'::jsonb; -- OK + jsonb +------- + "abc" +(1 row) + +SELECT '"abc'::jsonb; -- ERROR, quotes not closed +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc'::jsonb; + ^ +DETAIL: Token ""abc" is invalid. +CONTEXT: JSON data, line 1: "abc +SELECT '"abc +def"'::jsonb; -- ERROR, unescaped newline in string constant +ERROR: invalid input syntax for type json +LINE 1: SELECT '"abc + ^ +DETAIL: Character with value 0x0a must be escaped. +CONTEXT: JSON data, line 1: "abc +SELECT '"\n\"\\"'::jsonb; -- OK, legal escapes + jsonb +---------- + "\n\"\\" +(1 row) + +SELECT '"\v"'::jsonb; -- ERROR, not a valid JSON escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\v"'::jsonb; + ^ +DETAIL: Escape sequence "\v" is invalid. +CONTEXT: JSON data, line 1: "\v... +SELECT '"\u"'::jsonb; -- ERROR, incomplete escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u" +SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u00"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u00" +SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit +ERROR: invalid input syntax for type json +LINE 1: SELECT '"\u000g"'::jsonb; + ^ +DETAIL: "\u" must be followed by four hexadecimal digits. +CONTEXT: JSON data, line 1: "\u000g... +SELECT '"\u0000"'::jsonb; -- OK, legal escape + jsonb +----------- + "\\u0000" +(1 row) + +-- use octet_length here so we don't get an odd unicode char in the +-- output +SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK +ERROR: invalid input syntax for type json +LINE 1: SELECT octet_length('"\uaBcD"'::jsonb::text); + ^ +DETAIL: Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8. +CONTEXT: JSON data, line 1: ... +-- Numbers. +SELECT '1'::jsonb; -- OK + jsonb +------- + 1 +(1 row) + +SELECT '0'::jsonb; -- OK + jsonb +------- + 0 +(1 row) + +SELECT '01'::jsonb; -- ERROR, not valid according to JSON spec +ERROR: invalid input syntax for type json +LINE 1: SELECT '01'::jsonb; + ^ +DETAIL: Token "01" is invalid. +CONTEXT: JSON data, line 1: 01 +SELECT '0.1'::jsonb; -- OK + jsonb +------- + 0.1 +(1 row) + +SELECT '9223372036854775808'::jsonb; -- OK, even though it's too large for int8 + jsonb +--------------------- + 9223372036854775808 +(1 row) + +SELECT '1e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1.3e100'::jsonb; -- OK + jsonb +------------------------------------------------------------------------------------------------------- + 13000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +(1 row) + +SELECT '1f2'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1f2'::jsonb; + ^ +DETAIL: Token "1f2" is invalid. +CONTEXT: JSON data, line 1: 1f2 +SELECT '0.x1'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '0.x1'::jsonb; + ^ +DETAIL: Token "0.x1" is invalid. +CONTEXT: JSON data, line 1: 0.x1 +SELECT '1.3ex100'::jsonb; -- ERROR +ERROR: invalid input syntax for type json +LINE 1: SELECT '1.3ex100'::jsonb; + ^ +DETAIL: Token "1.3ex100" is invalid. +CONTEXT: JSON data, line 1: 1.3ex100 +-- Arrays. +SELECT '[]'::jsonb; -- OK + jsonb +------- + [] +(1 row) + +SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb; -- OK + jsonb +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + [[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] +(1 row) + +SELECT '[1,2]'::jsonb; -- OK + jsonb +-------- + [1, 2] +(1 row) + +SELECT '[1,2,]'::jsonb; -- ERROR, trailing comma +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2,]'::jsonb; + ^ +DETAIL: Expected JSON value, but found "]". +CONTEXT: JSON data, line 1: [1,2,] +SELECT '[1,2'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,2'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,2 +SELECT '[1,[2]'::jsonb; -- ERROR, no closing bracket +ERROR: invalid input syntax for type json +LINE 1: SELECT '[1,[2]'::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: [1,[2] +-- Objects. +SELECT '{}'::jsonb; -- OK + jsonb +------- + {} +(1 row) + +SELECT '{"abc"}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"}'::jsonb; + ^ +DETAIL: Expected ":", but found "}". +CONTEXT: JSON data, line 1: {"abc"} +SELECT '{"abc":1}'::jsonb; -- OK + jsonb +------------ + {"abc": 1} +(1 row) + +SELECT '{1:"abc"}'::jsonb; -- ERROR, keys must be strings +ERROR: invalid input syntax for type json +LINE 1: SELECT '{1:"abc"}'::jsonb; + ^ +DETAIL: Expected string or "}", but found "1". +CONTEXT: JSON data, line 1: {1... +SELECT '{"abc",1}'::jsonb; -- ERROR, wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc",1}'::jsonb; + ^ +DETAIL: Expected ":", but found ",". +CONTEXT: JSON data, line 1: {"abc",... +SELECT '{"abc"=1}'::jsonb; -- ERROR, totally wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"=1}'::jsonb; + ^ +DETAIL: Token "=" is invalid. +CONTEXT: JSON data, line 1: {"abc"=... +SELECT '{"abc"::1}'::jsonb; -- ERROR, another wrong separator +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc"::1}'::jsonb; + ^ +DETAIL: Expected JSON value, but found ":". +CONTEXT: JSON data, line 1: {"abc"::... +SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK + jsonb +-------------------------------------------------------------------- + {"abc": 1, "def": 2, "ghi": [3, 4], "hij": {"klm": 5, "nop": [6]}} +(1 row) + +SELECT '{"abc":1:2}'::jsonb; -- ERROR, colon in wrong spot +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1:2}'::jsonb; + ^ +DETAIL: Expected "," or "}", but found ":". +CONTEXT: JSON data, line 1: {"abc":1:... +SELECT '{"abc":1,3}'::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT '{"abc":1,3}'::jsonb; + ^ +DETAIL: Expected string, but found "3". +CONTEXT: JSON data, line 1: {"abc":1,3... +-- Miscellaneous stuff. +SELECT 'true'::jsonb; -- OK + jsonb +------- + true +(1 row) + +SELECT 'false'::jsonb; -- OK + jsonb +------- + false +(1 row) + +SELECT 'null'::jsonb; -- OK + jsonb +------- + null +(1 row) + +SELECT ' true '::jsonb; -- OK, even with extra whitespace + jsonb +------- + true +(1 row) + +SELECT 'true false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true false'::jsonb; + ^ +DETAIL: Expected end of input, but found "false". +CONTEXT: JSON data, line 1: true false +SELECT 'true, false'::jsonb; -- ERROR, too many values +ERROR: invalid input syntax for type json +LINE 1: SELECT 'true, false'::jsonb; + ^ +DETAIL: Expected end of input, but found ",". +CONTEXT: JSON data, line 1: true,... +SELECT 'truf'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'truf'::jsonb; + ^ +DETAIL: Token "truf" is invalid. +CONTEXT: JSON data, line 1: truf +SELECT 'trues'::jsonb; -- ERROR, not a keyword +ERROR: invalid input syntax for type json +LINE 1: SELECT 'trues'::jsonb; + ^ +DETAIL: Token "trues" is invalid. +CONTEXT: JSON data, line 1: trues +SELECT ''::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ''::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT ' '::jsonb; -- ERROR, no value +ERROR: invalid input syntax for type json +LINE 1: SELECT ' '::jsonb; + ^ +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +-- make sure jsonb is passed through json generators without being escaped +SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); + array_to_json +-------------------------- + [{"a": 1},{"b": [2, 3]}] +(1 row) + +-- jsonb extraction functions +CREATE TEMP TABLE test_jsonb ( + json_type text, + test_json jsonb +); +INSERT INTO test_jsonb VALUES +('scalar','"a scalar"'), +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_field (jsonb -> text operator) on a scalar +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_field (jsonb -> text operator) on an array +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + "val2" +(1 row) + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_field_text (jsonb ->> text operator) on a scalar +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_field_text (jsonb ->> text operator) on an array +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + val2 +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on a scalar +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + "two" +(1 row) + +SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + +(1 row) + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object'; +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on an object +SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object'; + ?column? +---------- + 4 +(1 row) + +SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object'; + ?column? +----------- + {"f1": 9} +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_array_element_text on a scalar +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array'; + ?column? +---------- + two +(1 row) + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object'; +ERROR: cannot call jsonb_array_element_text on an object +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar'; +ERROR: cannot call jsonb_object_keys on a scalar +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array'; +ERROR: cannot call jsonb_object_keys on an array +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; + jsonb_object_keys +------------------- + field1 + field2 + field3 + field4 + field5 + field6 +(6 rows) + +-- nulls +SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object'; + expect_true +------------- + t +(1 row) + +SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; + expect_false +-------------- + f +(1 row) + +SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; + expect_true +------------- + t +(1 row) + +-- equality and inequality +SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb; + ?column? +---------- + f +(1 row) + +SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; + ?column? +---------- + t +(1 row) + +CREATE TABLE testjsonb (j jsonb); +\copy testjsonb FROM 'data/jsonb.data' +-- containment +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); + jsonb_contains +---------------- + t +(1 row) + +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); + jsonb_contains +---------------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + ?column? +---------- + f +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + t +(1 row) + +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); + jsonb_contained +----------------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; + ?column? +---------- + f +(1 row) + +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; + ?column? +---------- + t +(1 row) + +SELECT '5'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +SELECT '[5]'::jsonb @> '5'; + ?column? +---------- + t +(1 row) + +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; + ?column? +---------- + f +(1 row) + +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; + ?column? +---------- + t +(1 row) + +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; + ?column? +---------- + t +(1 row) + +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; + ?column? +---------- + f +(1 row) + +-- array length +SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); + jsonb_array_length +-------------------- + 5 +(1 row) + +SELECT jsonb_array_length('[]'); + jsonb_array_length +-------------------- + 0 +(1 row) + +SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}'); +ERROR: cannot get array length of a non-array +SELECT jsonb_array_length('4'); +ERROR: cannot get array length of a scalar +-- each +SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}'); + jsonb_each +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,null) +(3 rows) + +SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,"""first""") + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,"""cc""") + (n,null) +(5 rows) + +SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | null + f5 | 99 + f6 | "stringy" +(5 rows) + +SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | "first" + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | "cc" + n | null +(5 rows) + +SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}'); + jsonb_each_text +-------------------- + (f1,"[1, 2, 3]") + (f2,"{""f3"": 1}") + (f4,) + (f5,null) +(4 rows) + +SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + q +------------------------------------------------------ + (1,first) + (a,"{""1"": ""first"", ""b"": ""c"", ""c"": ""b""}") + (b,"[1, 2]") + (c,cc) + (n,) +(5 rows) + +SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; + key | value +-----+----------- + f1 | [1, 2, 3] + f2 | {"f3": 1} + f4 | + f5 | 99 + f6 | stringy +(5 rows) + +SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + key | value +-----+------------------------------------ + 1 | first + a | {"1": "first", "b": "c", "c": "b"} + b | [1, 2] + c | cc + n | +(5 rows) + +-- exists +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); + jsonb_exists +-------------- + f +(1 row) + +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); + jsonb_exists +-------------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; + ?column? +---------- + t +(1 row) + +-- array exists - array elements should behave as keys +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_any +------------------ + t +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_any +------------------ + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + ?column? +---------- + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); + jsonb_exists_all +------------------ + f +(1 row) + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); + jsonb_exists_all +------------------ + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; + ?column? +---------- + t +(1 row) + +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + ?column? +---------- + t +(1 row) + +-- typeof +SELECT jsonb_typeof('{}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object; + object +-------- + object +(1 row) + +SELECT jsonb_typeof('[]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('["a", 1]') AS array; + array +------- + array +(1 row) + +SELECT jsonb_typeof('null') AS "null"; + null +------ + null +(1 row) + +SELECT jsonb_typeof('1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('1e2') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('-1.0') AS number; + number +-------- + number +(1 row) + +SELECT jsonb_typeof('true') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('false') AS boolean; + boolean +--------- + boolean +(1 row) + +SELECT jsonb_typeof('"hello"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"true"') AS string; + string +-------- + string +(1 row) + +SELECT jsonb_typeof('"1.0"') AS string; + string +-------- + string +(1 row) + +-- extract_path, extract_path_as_text +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path +-------------------- + "stringy" +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path +-------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path +-------------------- + "f3" +(1 row) + +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path +-------------------- + 1 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); + jsonb_extract_path_text +------------------------- + stringy +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); + jsonb_extract_path_text +------------------------- + {"f3": 1} +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); + jsonb_extract_path_text +------------------------- + f3 +(1 row) + +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + jsonb_extract_path_text +------------------------- + 1 +(1 row) + +-- extract_path nulls +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; + expect_false +-------------- + f +(1 row) + +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; + expect_true +------------- + t +(1 row) + +-- extract_path operators +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; + ?column? +----------- + "stringy" +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; + ?column? +---------- + "f3" +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; + ?column? +---------- + stringy +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; + ?column? +---------- + f3 +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + ?column? +---------- + 1 +(1 row) + +-- same using array literals +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}'; + ?column? +----------- + "stringy" +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}'; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}'; + ?column? +---------- + "f3" +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}'; + ?column? +---------- + stringy +(1 row) + +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}'; + ?column? +----------- + {"f3": 1} +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}'; + ?column? +---------- + f3 +(1 row) + +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}'; + ?column? +---------- + 1 +(1 row) + +-- same on jsonb scalars (expecting errors) +SELECT '42'::jsonb#>array['f2']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>array['0']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>>array['f2']; +ERROR: cannot call extract path from a scalar +SELECT '42'::jsonb#>>array['0']; +ERROR: cannot call extract path from a scalar +-- array_elements +SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); + jsonb_array_elements +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + null + {"f1": 1, "f2": [7, 8, 9]} + false +(6 rows) + +SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]'); + jsonb_array_elements_text +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q; + value +---------------------------- + 1 + true + [1, [2, 3]] + + {"f1": 1, "f2": [7, 8, 9]} + false + stringy +(7 rows) + +-- populate_record +CREATE TYPE jbpop AS (a text, b int, c timestamp); +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q; + a | b | c +--------+---+--- + blurfl | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q; + a | b | c +--------+---+-------------------------- + blurfl | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; + a | b | c +-------------------+---+--- + [100, 200, false] | | +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; + a | b | c +-------------------+---+-------------------------- + [100, 200, false] | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q; +ERROR: invalid input syntax for type timestamp: "[100, 200, false]" +-- populate_recordset +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + a | b | c +-----------------+----+-------------------------- + [100, 200, 300] | 99 | + {"z": true} | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; +ERROR: invalid input syntax for type timestamp: "[100, 200, 300]" +-- using the default use_json_as_text argument +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+---+-------------------------- + blurfl | | + | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; + a | b | c +--------+----+-------------------------- + blurfl | 99 | + def | 3 | Fri Jan 20 10:42:53 2012 +(2 rows) + +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +ERROR: cannot populate with a nested object unless use_json_as_text is true +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +ERROR: cannot populate with a nested object unless use_json_as_text is true +-- handling of unicode surrogate pairs +SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8; +ERROR: invalid input syntax for type json +LINE 1: SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc3... + ^ +DETAIL: Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; + ^ +DETAIL: Unicode high surrogate must not follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; + ^ +DETAIL: Unicode low surrogate must follow a high surrogate. +CONTEXT: JSON data, line 1: { "a":... +-- handling of simple unicode escapes +SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8; +ERROR: invalid input syntax for type json +LINE 1: SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a'... + ^ +DETAIL: Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8. +CONTEXT: JSON data, line 1: { "a":... +SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE; + correct_everywhere +-------------------- + dollar $ character +(1 row) + +SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' AS not_unescaped; + not_unescaped +-------------------- + null \u0000 escape +(1 row) + +-- indexing +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +CREATE INDEX jidx ON testjsonb USING gin (j); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}'; + count +------- + 3 +(1 row) + +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + count +------- + 1009 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ? 'public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +-- array exists - array elements should behave as keys (for GIN index scans too) +CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; + count +------- + 3 +(1 row) + +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; + count +------- + 0 +(1 row) + +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + count +------- + 1 +(1 row) + +RESET enable_seqscan; +SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; + count +------- + 4788 +(1 row) + +SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key; + key | count +-----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + abstract | 161 + array | 5 + age | 2 +(24 rows) + +-- sort/hash +SELECT count(distinct j) FROM testjsonb; + count +------- + 891 +(1 row) + +SET enable_hashagg = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 891 +(1 row) + +SET enable_hashagg = on; +SET enable_sort = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; + count +------- + 891 +(1 row) + +SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); + j +---- + {} +(1 row) + +SET enable_sort = on; +RESET enable_hashagg; +RESET enable_sort; +DROP INDEX jidx; +DROP INDEX jidx_array; +-- btree +CREATE INDEX jidx ON testjsonb USING btree (j); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j > '{"p":1}'; + count +------- + 884 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}'; + count +------- + 1 +(1 row) + +--gin hash +DROP INDEX jidx; +CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops); +SET enable_seqscan = off; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; + count +------- + 2 +(1 row) + +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + count +------- + 1009 +(1 row) + +RESET enable_seqscan; +DROP INDEX jidx; +-- nested tests +SELECT '{"ff":{"a":12,"b":16}}'::jsonb; + jsonb +---------------------------- + {"ff": {"a": 12, "b": 16}} +(1 row) + +SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb; + jsonb +--------------------------------------- + {"ff": {"a": 12, "b": 16}, "qq": 123} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb; + jsonb +-------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": 12, "b": 16, "c": ["c1", "c2"], "d": {"d1": "d3", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------ + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2"], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; + jsonb +------------------------------------------------------------------------------------------------------------------------- + {"aa": ["a", "aaa"], "qq": {"a": "12", "b": "16", "c": ["c1", "c2", ["c3"], {"c4": 4}], "d": {"d1": "d1", "d2": "d2"}}} +(1 row) + +SELECT '{"ff":["a","aaa"]}'::jsonb; + jsonb +---------------------- + {"ff": ["a", "aaa"]} +(1 row) + +SELECT + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff', + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq', + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f, + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t, + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; + ?column? | ?column? | f | t | ?column? +--------------------+----------+---+---+---------- + {"a": 12, "b": 16} | 123 | f | t | [1, 2] +(1 row) + +-- nested containment +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; + ?column? +---------- + f +(1 row) + +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; + ?column? +---------- + t +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; + ?column? +---------- + f +(1 row) + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; + ?column? +---------- + t +(1 row) + +-- nested object field / array index lookup +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n'; + ?column? +---------- + null +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b'; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c'; + ?column? +---------- + {"1": 2} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd'; + ?column? +--------------- + {"1": [2, 3]} +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1'; + ?column? +---------- + [2, 3] +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e'; + ?column? +---------- + +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error +ERROR: cannot call jsonb_array_element (jsonb -> int operator) on an object +SELECT '["a","b","c",[1,2],null]'::jsonb -> 0; + ?column? +---------- + "a" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 1; + ?column? +---------- + "b" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 2; + ?column? +---------- + "c" +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3; + ?column? +---------- + [1, 2] +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1; + ?column? +---------- + 2 +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 4; + ?column? +---------- + null +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 5; + ?column? +---------- + +(1 row) + +SELECT '["a","b","c",[1,2],null]'::jsonb -> -1; + ?column? +---------- + +(1 row) + +--nested path extraction +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; + ?column? +---------- + "b" +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; + ?column? +----------- + [1, 2, 3] +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; + ?column? +---------- + 1 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; + ?column? +---------- + 2 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; + ?column? +---------- + 3 +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; + ?column? +---------- + +(1 row) + +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; + ?column? +---------- + +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; + ?column? +---------- + 0 +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; + ?column? +---------- + [3, 4] +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; + ?column? +--------------- + {"5": "five"} +(1 row) + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; + ?column? +---------- + "five" +(1 row) + +--nested exists +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; + ?column? +---------- + t +(1 row) + +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e'; + ?column? +---------- + f +(1 row) + diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 5627b4a426..bf76501435 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1127,6 +1127,10 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 7 | @> + 2742 | 9 | ? + 2742 | 10 | ?| + 2742 | 11 | ?& 4000 | 1 | << 4000 | 1 | ~<~ 4000 | 2 | &< @@ -1149,7 +1153,7 @@ ORDER BY 1, 2, 3; 4000 | 15 | > 4000 | 16 | @> 4000 | 18 | = -(67 rows) +(71 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 2e3eba83de..c62be2a023 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -98,8 +98,7 @@ test: event_trigger # ---------- # Another group of parallel tests # ---------- -test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json indirect_toast - +test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb indirect_toast # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 4f1dedec2b..885ca9aa5d 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -122,6 +122,7 @@ test: xmlmap test: functional_deps test: advisory_lock test: json +test: jsonb test: indirect_toast test: plancache test: limit diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 2d3f0bcc61..2ae5b82799 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -136,8 +136,8 @@ CREATE TEMP TABLE test_json ( INSERT INTO test_json VALUES ('scalar','"a scalar"'), -('array','["zero", "one","two",null,"four","five"]'), -('object','{"field1":"val1","field2":"val2","field3":null}'); +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); SELECT test_json -> 'x' FROM test_json @@ -175,6 +175,13 @@ SELECT test_json->>2 FROM test_json WHERE json_type = 'array'; +SELECT test_json ->> 6 FROM test_json WHERE json_type = 'array'; +SELECT test_json ->> 7 FROM test_json WHERE json_type = 'array'; + +SELECT test_json ->> 'field4' FROM test_json WHERE json_type = 'object'; +SELECT test_json ->> 'field5' FROM test_json WHERE json_type = 'object'; +SELECT test_json ->> 'field6' FROM test_json WHERE json_type = 'object'; + SELECT json_object_keys(test_json) FROM test_json WHERE json_type = 'scalar'; @@ -187,6 +194,13 @@ SELECT json_object_keys(test_json) FROM test_json WHERE json_type = 'object'; +-- test extending object_keys resultset - initial resultset size is 256 + +select count(*) from + (select json_object_keys(json_object(array_agg(g))) + from (select unnest(array['f'||n,n::text])as g + from generate_series(1,300) as n) x ) y; + -- nulls select (test_json->'field3') is null as expect_false diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql new file mode 100644 index 0000000000..1ef49138bb --- /dev/null +++ b/src/test/regress/sql/jsonb.sql @@ -0,0 +1,479 @@ +-- Strings. +SELECT '""'::jsonb; -- OK. +SELECT $$''$$::jsonb; -- ERROR, single quotes are not allowed +SELECT '"abc"'::jsonb; -- OK +SELECT '"abc'::jsonb; -- ERROR, quotes not closed +SELECT '"abc +def"'::jsonb; -- ERROR, unescaped newline in string constant +SELECT '"\n\"\\"'::jsonb; -- OK, legal escapes +SELECT '"\v"'::jsonb; -- ERROR, not a valid JSON escape +SELECT '"\u"'::jsonb; -- ERROR, incomplete escape +SELECT '"\u00"'::jsonb; -- ERROR, incomplete escape +SELECT '"\u000g"'::jsonb; -- ERROR, g is not a hex digit +SELECT '"\u0000"'::jsonb; -- OK, legal escape +-- use octet_length here so we don't get an odd unicode char in the +-- output +SELECT octet_length('"\uaBcD"'::jsonb::text); -- OK, uppercase and lower case both OK + +-- Numbers. +SELECT '1'::jsonb; -- OK +SELECT '0'::jsonb; -- OK +SELECT '01'::jsonb; -- ERROR, not valid according to JSON spec +SELECT '0.1'::jsonb; -- OK +SELECT '9223372036854775808'::jsonb; -- OK, even though it's too large for int8 +SELECT '1e100'::jsonb; -- OK +SELECT '1.3e100'::jsonb; -- OK +SELECT '1f2'::jsonb; -- ERROR +SELECT '0.x1'::jsonb; -- ERROR +SELECT '1.3ex100'::jsonb; -- ERROR + +-- Arrays. +SELECT '[]'::jsonb; -- OK +SELECT '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'::jsonb; -- OK +SELECT '[1,2]'::jsonb; -- OK +SELECT '[1,2,]'::jsonb; -- ERROR, trailing comma +SELECT '[1,2'::jsonb; -- ERROR, no closing bracket +SELECT '[1,[2]'::jsonb; -- ERROR, no closing bracket + +-- Objects. +SELECT '{}'::jsonb; -- OK +SELECT '{"abc"}'::jsonb; -- ERROR, no value +SELECT '{"abc":1}'::jsonb; -- OK +SELECT '{1:"abc"}'::jsonb; -- ERROR, keys must be strings +SELECT '{"abc",1}'::jsonb; -- ERROR, wrong separator +SELECT '{"abc"=1}'::jsonb; -- ERROR, totally wrong separator +SELECT '{"abc"::1}'::jsonb; -- ERROR, another wrong separator +SELECT '{"abc":1,"def":2,"ghi":[3,4],"hij":{"klm":5,"nop":[6]}}'::jsonb; -- OK +SELECT '{"abc":1:2}'::jsonb; -- ERROR, colon in wrong spot +SELECT '{"abc":1,3}'::jsonb; -- ERROR, no value + +-- Miscellaneous stuff. +SELECT 'true'::jsonb; -- OK +SELECT 'false'::jsonb; -- OK +SELECT 'null'::jsonb; -- OK +SELECT ' true '::jsonb; -- OK, even with extra whitespace +SELECT 'true false'::jsonb; -- ERROR, too many values +SELECT 'true, false'::jsonb; -- ERROR, too many values +SELECT 'truf'::jsonb; -- ERROR, not a keyword +SELECT 'trues'::jsonb; -- ERROR, not a keyword +SELECT ''::jsonb; -- ERROR, no value +SELECT ' '::jsonb; -- ERROR, no value + +-- make sure jsonb is passed through json generators without being escaped +SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']); + +-- jsonb extraction functions +CREATE TEMP TABLE test_jsonb ( + json_type text, + test_json jsonb +); + +INSERT INTO test_jsonb VALUES +('scalar','"a scalar"'), +('array','["zero", "one","two",null,"four","five", [1,2,3],{"f1":9}]'), +('object','{"field1":"val1","field2":"val2","field3":null, "field4": 4, "field5": [1,2,3], "field6": {"f1":9}}'); + +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json -> 'field2' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 9 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json -> 2 FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 6 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 7 FROM test_jsonb WHERE json_type = 'array'; + +SELECT test_json ->> 'field4' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json ->> 'field5' FROM test_jsonb WHERE json_type = 'object'; +SELECT test_json ->> 'field6' FROM test_jsonb WHERE json_type = 'object'; + +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'scalar'; +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'array'; +SELECT test_json ->> 2 FROM test_jsonb WHERE json_type = 'object'; + +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'scalar'; +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'array'; +SELECT jsonb_object_keys(test_json) FROM test_jsonb WHERE json_type = 'object'; + +-- nulls +SELECT (test_json->'field3') IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json->>'field3') IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'object'; +SELECT (test_json->3) IS NULL AS expect_false FROM test_jsonb WHERE json_type = 'array'; +SELECT (test_json->>3) IS NULL AS expect_true FROM test_jsonb WHERE json_type = 'array'; + +-- equality and inequality +SELECT '{"x":"y"}'::jsonb = '{"x":"y"}'::jsonb; +SELECT '{"x":"y"}'::jsonb = '{"x":"z"}'::jsonb; + +SELECT '{"x":"y"}'::jsonb <> '{"x":"y"}'::jsonb; +SELECT '{"x":"y"}'::jsonb <> '{"x":"z"}'::jsonb; + +CREATE TABLE testjsonb (j jsonb); +\copy testjsonb FROM 'data/jsonb.data' + +-- containment +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"g":null}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"c"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b"}'); +SELECT jsonb_contains('{"a":"b", "b":1, "c":null}', '{"a":"b", "c":"q"}'); +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"g":null}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"c"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b"}'; +SELECT '{"a":"b", "b":1, "c":null}'::jsonb @> '{"a":"b", "c":"q"}'; + +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"g":null}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"c"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b"}', '{"a":"b", "b":1, "c":null}'); +SELECT jsonb_contained('{"a":"b", "c":"q"}', '{"a":"b", "b":1, "c":null}'); +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"g":null}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"c"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +SELECT '{"a":"b", "c":"q"}'::jsonb <@ '{"a":"b", "b":1, "c":null}'; +-- Raw scalar may contain another raw scalar, array may contain a raw scalar +SELECT '[5]'::jsonb @> '[5]'; +SELECT '5'::jsonb @> '5'; +SELECT '[5]'::jsonb @> '5'; +-- But a raw scalar cannot contain an array +SELECT '5'::jsonb @> '[5]'; +-- In general, one thing should always contain itself. Test array containment: +SELECT '["9", ["7", "3"], 1]'::jsonb @> '["9", ["7", "3"], 1]'::jsonb; +SELECT '["9", ["7", "3"], ["1"]]'::jsonb @> '["9", ["7", "3"], ["1"]]'::jsonb; +-- array containment string matching confusion bug +SELECT '{ "name": "Bob", "tags": [ "enim", "qui"]}'::jsonb @> '{"tags":["qu"]}'; + +-- array length +SELECT jsonb_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]'); +SELECT jsonb_array_length('[]'); +SELECT jsonb_array_length('{"f1":1,"f2":[5,6]}'); +SELECT jsonb_array_length('4'); + +-- each +SELECT jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null}'); +SELECT jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; +SELECT * FROM jsonb_each('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; +SELECT * FROM jsonb_each('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + +SELECT jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":"null"}'); +SELECT jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; +SELECT * FROM jsonb_each_text('{"f1":[1,2,3],"f2":{"f3":1},"f4":null,"f5":99,"f6":"stringy"}') q; +SELECT * FROM jsonb_each_text('{"a":{"b":"c","c":"b","1":"first"},"b":[1,2],"c":"cc","1":"first","n":null}'::jsonb) AS q; + +-- exists +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'a'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'b'); +SELECT jsonb_exists('{"a":null, "b":"qq"}', 'c'); +SELECT jsonb_exists('{"a":"null", "b":"qq"}', 'a'); +SELECT jsonb '{"a":null, "b":"qq"}' ? 'a'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'b'; +SELECT jsonb '{"a":null, "b":"qq"}' ? 'c'; +SELECT jsonb '{"a":"null", "b":"qq"}' ? 'a'; +-- array exists - array elements should behave as keys +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_any('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?| '{}'::text[]; + +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['a','b']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['b','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','a']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', ARRAY['c','d']); +SELECT jsonb_exists_all('{"a":null, "b":"qq"}', '{}'::text[]); +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['b','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','a']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['c','d']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& ARRAY['a','a', 'b', 'b', 'b']; +SELECT jsonb '{"a":null, "b":"qq"}' ?& '{}'::text[]; + +-- typeof +SELECT jsonb_typeof('{}') AS object; +SELECT jsonb_typeof('{"c":3,"p":"o"}') AS object; +SELECT jsonb_typeof('[]') AS array; +SELECT jsonb_typeof('["a", 1]') AS array; +SELECT jsonb_typeof('null') AS "null"; +SELECT jsonb_typeof('1') AS number; +SELECT jsonb_typeof('-1') AS number; +SELECT jsonb_typeof('1.0') AS number; +SELECT jsonb_typeof('1e2') AS number; +SELECT jsonb_typeof('-1.0') AS number; +SELECT jsonb_typeof('true') AS boolean; +SELECT jsonb_typeof('false') AS boolean; +SELECT jsonb_typeof('"hello"') AS string; +SELECT jsonb_typeof('"true"') AS string; +SELECT jsonb_typeof('"1.0"') AS string; + +-- extract_path, extract_path_as_text +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f4','f6'); +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}','f2'); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',0::text); +SELECT jsonb_extract_path_text('{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}','f2',1::text); + +-- extract_path nulls +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":{"f5":null,"f6":"stringy"}}','f4','f5') IS NULL AS expect_true; +SELECT jsonb_extract_path('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_false; +SELECT jsonb_extract_path_text('{"f2":{"f3":1},"f4":[0,1,2,null]}','f4','3') IS NULL AS expect_true; + +-- extract_path operators +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>array['f2','1']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f4','f6']; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','0']; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>array['f2','1']; + +-- same using array literals +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f4,f6}'; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2}'; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,0}'; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>'{f2,1}'; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f4,f6}'; +SELECT '{"f2":{"f3":1},"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2}'; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,0}'; +SELECT '{"f2":["f3",1],"f4":{"f5":99,"f6":"stringy"}}'::jsonb#>>'{f2,1}'; + +-- same on jsonb scalars (expecting errors) +SELECT '42'::jsonb#>array['f2']; +SELECT '42'::jsonb#>array['0']; +SELECT '42'::jsonb#>>array['f2']; +SELECT '42'::jsonb#>>array['0']; + +-- array_elements +SELECT jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]'); +SELECT * FROM jsonb_array_elements('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q; +SELECT jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]'); +SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false,"stringy"]') q; + +-- populate_record +CREATE TYPE jbpop AS (a text, b int, c timestamp); + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}', true) q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}', true) q; + +SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}', true) q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}', true) q; + +-- populate_recordset +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',false) q; +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]',true) q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]',true) q; + +-- using the default use_json_as_text argument +SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; + + +-- handling of unicode surrogate pairs +SELECT octet_length((jsonb '{ "a": "\ud83d\ude04\ud83d\udc36" }' -> 'a')::text) AS correct_in_utf8; +SELECT jsonb '{ "a": "\ud83d\ud83d" }' -> 'a'; -- 2 high surrogates in a row +SELECT jsonb '{ "a": "\ude04\ud83d" }' -> 'a'; -- surrogates in wrong order +SELECT jsonb '{ "a": "\ud83dX" }' -> 'a'; -- orphan high surrogate +SELECT jsonb '{ "a": "\ude04X" }' -> 'a'; -- orphan low surrogate + +-- handling of simple unicode escapes +SELECT jsonb '{ "a": "the Copyright \u00a9 sign" }' ->> 'a' AS correct_in_utf8; +SELECT jsonb '{ "a": "dollar \u0024 character" }' ->> 'a' AS correct_everyWHERE; +SELECT jsonb '{ "a": "null \u0000 escape" }' ->> 'a' AS not_unescaped; + +-- indexing +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +SELECT count(*) FROM testjsonb WHERE j ? 'public'; +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + +CREATE INDEX jidx ON testjsonb USING gin (j); +SET enable_seqscan = off; + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["foo"]}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"array":["bar"]}'; +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; +SELECT count(*) FROM testjsonb WHERE j ? 'public'; +SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; + +-- array exists - array elements should behave as keys (for GIN index scans too) +CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); +SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; +-- type sensitive array exists - should return no rows (since "exists" only +-- matches strings that are either object keys or array elements) +SELECT count(*) from testjsonb WHERE j->'array' ? '5'::text; +-- However, a raw scalar is *contained* within the array +SELECT count(*) from testjsonb WHERE j->'array' @> '5'::jsonb; + +RESET enable_seqscan; + +SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; +SELECT key, count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow GROUP BY key ORDER BY count DESC, key; + +-- sort/hash +SELECT count(distinct j) FROM testjsonb; +SET enable_hashagg = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; +SET enable_hashagg = on; +SET enable_sort = off; +SELECT count(*) FROM (SELECT j FROM (SELECT * FROM testjsonb UNION ALL SELECT * FROM testjsonb) js GROUP BY j) js2; +SELECT distinct * FROM (values (jsonb '{}' || ''),('{}')) v(j); +SET enable_sort = on; + +RESET enable_hashagg; +RESET enable_sort; + +DROP INDEX jidx; +DROP INDEX jidx_array; +-- btree +CREATE INDEX jidx ON testjsonb USING btree (j); +SET enable_seqscan = off; + +SELECT count(*) FROM testjsonb WHERE j > '{"p":1}'; +SELECT count(*) FROM testjsonb WHERE j = '{"pos":98, "line":371, "node":"CBA", "indexed":true}'; + +--gin hash +DROP INDEX jidx; +CREATE INDEX jidx ON testjsonb USING gin (j jsonb_hash_ops); +SET enable_seqscan = off; + +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC", "public":true}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25}'; +SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; +-- excercise GIN_SEARCH_MODE_ALL +SELECT count(*) FROM testjsonb WHERE j @> '{}'; + +RESET enable_seqscan; +DROP INDEX jidx; + +-- nested tests +SELECT '{"ff":{"a":12,"b":16}}'::jsonb; +SELECT '{"ff":{"a":12,"b":16},"qq":123}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":12,"b":16,"c":["c1","c2"],"d":{"d1":"d1","d2":"d2","d1":"d3"}}}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2"],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; +SELECT '{"aa":["a","aaa"],"qq":{"a":"12","b":"16","c":["c1","c2",["c3"],{"c4":4}],"d":{"d1":"d1","d2":"d2"}}}'::jsonb; +SELECT '{"ff":["a","aaa"]}'::jsonb; + +SELECT + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'ff', + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'qq', + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'Y') IS NULL AS f, + ('{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb ->> 'Y') IS NULL AS t, + '{"ff":{"a":12,"b":16},"qq":123,"x":[1,2],"Y":null}'::jsonb -> 'x'; + +-- nested containment +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":[2,1],"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":[1,2]}'; +SELECT '{"a":{"1":2},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '{"a":{"2":1},"c":"b"}'::jsonb @> '{"a":{"1":2}}'; +SELECT '["a","b"]'::jsonb @> '["a","b","c","b"]'; +SELECT '["a","b","c","b"]'::jsonb @> '["a","b"]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["a",[1,2]]'; +SELECT '["a","b","c",[1,2]]'::jsonb @> '["b",[1,2]]'; + +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[1]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[2]}'; +SELECT '{"a":[1,2],"c":"b"}'::jsonb @> '{"a":[3]}'; + +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"c":3}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4}]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},3]}'; +SELECT '{"a":[1,2,{"c":3,"x":4}],"c":"b"}'::jsonb @> '{"a":[{"x":4},1]}'; + +-- nested object field / array index lookup +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'n'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'a'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'b'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'c'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'd' -> '1'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 'e'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb -> 0; --expecting error + +SELECT '["a","b","c",[1,2],null]'::jsonb -> 0; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 1; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 2; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 3 -> 1; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 4; +SELECT '["a","b","c",[1,2],null]'::jsonb -> 5; +SELECT '["a","b","c",[1,2],null]'::jsonb -> -1; + +--nested path extraction +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{a}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,0}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,1}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,2}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,3}'; +SELECT '{"a":"b","c":[1,2,3]}'::jsonb #> '{c,-1}'; + +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{0}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{3}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4}'; +SELECT '[0,1,2,[3,4],{"5":"five"}]'::jsonb #> '{4,5}'; + +--nested exists +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'n'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'a'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'b'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'c'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'd'; +SELECT '{"n":null,"a":1,"b":[1,2],"c":{"1":2},"d":{"1":[2,3]}}'::jsonb ? 'e';