*** a/doc/src/sgml/datatype.sgml --- b/doc/src/sgml/datatype.sgml *************** *** 139,145 **** json ! JSON data --- 139,151 ---- json ! textual JSON data ! ! ! ! jsonb ! ! binary JSON data, decomposed *************** SET xmloption TO { DOCUMENT | CONTENT }; *** 4220,4253 **** ! ! <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. ! ! &array; --- 4226,4232 ---- ! &json; &array; *** a/doc/src/sgml/filelist.sgml --- b/doc/src/sgml/filelist.sgml *************** *** 22,27 **** --- 22,28 ---- + *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** table2-mapping *** 10065,10076 **** ! shows the operators that are ! available for use with JSON (see ) data. ! JSON Operators --- 10065,10077 ---- ! shows the operators that ! are available for use with the two JSON datatypes (see ).
! <type>json</> and <type>jsonb</> Operators *************** table2-mapping *** 10107,10119 **** #> ! array of text Get JSON object at specified path '{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}' #>> ! array of text Get JSON object at specified path as text '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' --- 10108,10120 ---- #> ! text[] Get JSON object at specified path '{"a":[1,2,3],"b":[4,5,6]}'::json#>'{a,2}' #>> ! text[] Get JSON object at specified path as text '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' *************** table2-mapping *** 10121,10133 ****
! shows the functions that are available ! for creating and manipulating JSON (see ) data. ! ! JSON Support Functions --- 10122,10227 ----
+ + + 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 various + GiST and GIN operator + classes for indexing). + + ! The following are jsonb-only operators, used by ! jsonb operator classes. ! 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 exist? + '{"a":1, "b":2}'::jsonb ? 'b' + + + ?| + text[] + Do any of these keys/elements exist? + '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] + + + ?& + text[] + Do all of these keys/elements exist? + '["a", "b"]'::jsonb ?& array['a', 'b'] + + + +
! ! ! shows the functions that are ! available for creating json values. ! (see ) ! ! ! ! array_to_json ! ! ! row_to_json ! ! ! to_json ! ! ! json_build_array ! ! ! json_build_object ! ! ! json_object ! ! ! ! JSON Creation Functions *************** table2-mapping *** 10141,10149 **** - - array_to_json - array_to_json(anyarray [, pretty_bool]) json --- 10235,10240 ---- *************** table2-mapping *** 10157,10165 **** - - row_to_json - row_to_json(record [, pretty_bool]) json --- 10248,10253 ---- *************** table2-mapping *** 10172,10180 **** - - to_json - to_json(anyelement) json --- 10260,10265 ---- *************** table2-mapping *** 10190,10200 **** ! ! json_array_length ! ! json_array_length(json) int Returns the number of elements in the outermost JSON array. --- 10275,10454 ---- ! json_build_array(VARIADIC "any") ! ! json ! ! Builds a heterogeneously-typed json array out of a variadic argument list. ! ! SELECT json_build_array(1,2,'3',4,5); ! ! ! json_build_array ! ------------------- ! [1, 2, "3", 4, 5] ! ! ! ! ! ! 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. ! ! SELECT json_build_object('foo',1,'bar',2); ! ! ! json_build_object ! ------------------------ ! {"foo" : 1, "bar" : 2} ! ! ! ! ! ! json_object(text[]) ! ! json ! ! Builds a JSON object out of a text array. The array must have either ! exactly one dimension with an even number of members, in which case ! they are taken as alternating name/value pairs, or two dimensions ! 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 json_object('{{a, 1},{b, "def"},{c, 3.5}}') ! ! ! json_object ! --------------------------------------- ! {"a" : "1", "b" : "def", "c" : "3.5"} ! ! ! ! ! ! json_object(keys text[], values text[]) ! ! json ! ! 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 json_object('{a, b}', '{1,2}'); + + + json_object + ------------------------ + {"a" : "1", "b" : "2"} + + + + + +
+ + + + 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. *************** table2-mapping *** 10203,10215 **** 5 ! ! ! json_each ! ! json_each(json) ! ! SETOF key text, value json Expands the outermost JSON object into a set of key/value pairs. --- 10457,10468 ---- 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. *************** table2-mapping *** 10224,10235 **** ! ! ! 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 --- 10477,10485 ---- ! 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 *************** table2-mapping *** 10246,10258 **** ! ! ! json_extract_path ! ! json_extract_path(from_json json, VARIADIC path_elems text[]) ! ! json Returns JSON value pointed to by path_elems. --- 10496,10506 ---- ! 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. *************** table2-mapping *** 10260,10271 **** {"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. --- 10508,10516 ---- {"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. *************** table2-mapping *** 10274,10285 **** 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. --- 10519,10527 ---- 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. *************** table2-mapping *** 10295,10312 **** ! ! ! 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}') --- 10537,10552 ---- ! 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}') *************** table2-mapping *** 10318,10336 **** ! ! ! 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}]') --- 10558,10574 ---- ! 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}]') *************** table2-mapping *** 10343,10355 **** ! ! ! json_array_elements ! ! json_array_elements(json) ! ! SETOF json Expands a JSON array to a set of JSON values. --- 10581,10592 ---- ! json_array_elements(json) ! jsonb_array_elements(jsonb) ! ! SETOF json ! SETOF jsonb ! Expands a JSON array to a set of JSON values. *************** table2-mapping *** 10365,10376 **** ! ! ! json_array_elements_text ! ! json_array_elements_text(json) ! SETOF text Expands a JSON array to a set of text values. --- 10602,10610 ---- ! json_array_elements_text(json) ! jsonb_array_elements_text(jsonb) ! SETOF text Expands a JSON array to a set of text values. *************** table2-mapping *** 10386,10397 **** ! ! ! json_typeof ! ! json_typeof(json) ! text Returns the type of the outermost JSON value as a text string. The types are --- 10620,10628 ---- ! json_typeof(json) ! jsonb_typeof(jsonb) ! text Returns the type of the outermost JSON value as a text string. The types are *************** table2-mapping *** 10404,10501 **** - - json_build_array - - json_build_array(VARIADIC "any") - - json - - Builds a heterogeneously-typed json array out of a variadic argument list. - - SELECT json_build_array(1,2,'3',4,5); - - - json_build_array - ------------------- - [1, 2, "3", 4, 5] - - - - - - - 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. - - SELECT json_build_object('foo',1,'bar',2); - - - json_build_object - ------------------------ - {"foo" : 1, "bar" : 2} - - - - - - - json_object - - json_object(text[]) - - json - - Builds a JSON object out of a text array. The array must have either - exactly one dimension with an even number of members, in which case - they are taken as alternating name/value pairs, or two dimensions - 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}}') - - - json_object - --------------------------------------- - {"a" : "1", "b" : "def", "c" : "3.5"} - - - - - - json_object(keys text[], values text[]) - - json - - 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}'); - - - json_object - ------------------------ - {"a" : "1", "b" : "2"} - - - - - - - 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 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. --- 10635,10645 ---- json_to_record(json, nested_as_text bool) record ! 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. *************** table2-mapping *** 10512,10525 **** - - 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 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. --- 10656,10666 ---- json_to_recordset(json, nested_as_text bool) setof record ! 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 *** a/doc/src/sgml/json.sgml --- b/doc/src/sgml/json.sgml *************** *** 0 **** --- 1,326 ---- + + + + <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 + GiST and 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. The danger of losing + numeric precision in respect of data originating in + PostgreSQL is such a system 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 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 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 values (primitive types) as + elements, keys and values. + + + + <type>jsonb</> GiST and GIN Indexing + + jsonb + indexes on + + + The jsonb-only indexable operators do not permit + searching for keys or elements that are not at the least-nested + level (except in the limited sense that JSON object key/value + pairs can be indexed at that same nesting + level). However, they can be used to efficiently search among + more than one possible key, or 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). + + + The nesting restriction is far less onerous than it might first + appear; expressional indexes can be used to support indexing of + nested elements. + + + jsonb has GiST and GIN index support for the + @>, ?, ?& and + ?| operators. The default GiST and GIN operator + classes makes all these operators indexable: + + + -- GiST index (default opclass) + CREATE INDEX idxgist ON api USING GIST (jdoc); + + -- 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, + "gender": "female", + "company": "Magnafone", + "address": "178 Howard Place, Gulf, Washington, 702", + "registered": "2009-11-07T08:53:22 +08:00", + "latitude": 19.793713, + "longitude": 86.513373, + "tags": [ + "aliquip", + "enim", + "aliquip", + "qui", + ] + } + + If a GiST or GIN index were to be 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: + + CREATE INDEX idxgin ON api USING GIN ((jdoc -> 'tags')); + + + + Expressional indexes are discussed in . + + + 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. + + + For full details of the semantics that these indexable operators + implement, see . + + + + <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 keys > object with n - 1 keys + + Array with n elements > array with n - 1 elements + + 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 keys are compared: + + key-1, value-1, key-2 ... + + Similarly, arrays with equal + numbers of elements are compared: + + element-1, element-2 ... + + + + *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** CREATE OR REPLACE FUNCTION *** 825,830 **** --- 825,838 ---- 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) *** a/src/backend/utils/adt/Makefile --- b/src/backend/utils/adt/Makefile *************** OBJS = acl.o arrayfuncs.o array_selfuncs *** 21,31 **** 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 \ 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 \ --- 21,31 ---- 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 jsonb.o jsonb_gin.o jsonb_gist.o jsonb_op.o \ ! jsonb_support.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 \ *** a/src/backend/utils/adt/json.c --- b/src/backend/utils/adt/json.c *************** Datum *** 210,231 **** 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); pg_parse_json(lex, &nullSemAction); ! PG_RETURN_TEXT_P(result); } /* --- 210,226 ---- json_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); char *str; int nbytes; JsonLexContext *lex; str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); /* Validate it. */ ! lex = makeJsonLexContextCstringLen(str, nbytes, false); pg_parse_json(lex, &nullSemAction); ! PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); } /* *************** json_recv(PG_FUNCTION_ARGS) *** 236,250 **** * * Without is better as it makes the processing faster, so only make one * if really required. */ JsonLexContext * makeJsonLexContext(text *json, bool need_escapes) { JsonLexContext *lex = palloc0(sizeof(JsonLexContext)); ! lex->input = lex->token_terminator = lex->line_start = VARDATA(json); lex->line_number = 1; ! lex->input_length = VARSIZE(json) - VARHDRSZ; if (need_escapes) lex->strval = makeStringInfo(); return lex; --- 231,256 ---- * * 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 = json; lex->line_number = 1; ! lex->input_length = len; if (need_escapes) lex->strval = makeStringInfo(); return lex; *************** datum_to_json(Datum val, bool is_null, S *** 1274,1280 **** pfree(outputstr); break; case TYPCATEGORY_JSON: ! /* JSON will already be escaped */ outputstr = OidOutputFunctionCall(typoutputfunc, val); appendStringInfoString(result, outputstr); pfree(outputstr); --- 1280,1286 ---- pfree(outputstr); break; case TYPCATEGORY_JSON: ! /* JSON and JSONB will already be escaped */ outputstr = OidOutputFunctionCall(typoutputfunc, val); appendStringInfoString(result, outputstr); pfree(outputstr); *************** array_to_json_internal(Datum array, Stri *** 1406,1412 **** tcategory = TYPCATEGORY_JSON_CAST; else if (element_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (element_type == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(element_type); --- 1412,1418 ---- tcategory = TYPCATEGORY_JSON_CAST; else if (element_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (element_type == JSONOID || element_type == JSONBOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(element_type); *************** composite_to_json(Datum composite, Strin *** 1501,1507 **** tcategory = TYPCATEGORY_ARRAY; else if (tupdesc->attrs[i]->atttypid == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (tupdesc->attrs[i]->atttypid == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); --- 1507,1514 ---- tcategory = TYPCATEGORY_ARRAY; else if (tupdesc->attrs[i]->atttypid == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (tupdesc->attrs[i]->atttypid == JSONOID || ! tupdesc->attrs[i]->atttypid == JSONBOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(tupdesc->attrs[i]->atttypid); *************** to_json(PG_FUNCTION_ARGS) *** 1689,1695 **** tcategory = TYPCATEGORY_ARRAY; else if (val_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (val_type == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(val_type); --- 1696,1702 ---- tcategory = TYPCATEGORY_ARRAY; else if (val_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (val_type == JSONOID || val_type == JSONBOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(val_type); *************** json_agg_transfn(PG_FUNCTION_ARGS) *** 1783,1789 **** tcategory = TYPCATEGORY_ARRAY; else if (val_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (val_type == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(val_type); --- 1790,1796 ---- tcategory = TYPCATEGORY_ARRAY; else if (val_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; ! else if (val_type == JSONOID || val_type == JSONBOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(val_type); *************** escape_json(StringInfo buf, const char * *** 2346,2357 **** Datum json_typeof(PG_FUNCTION_ARGS) { ! text *json = PG_GETARG_TEXT_P(0); ! JsonLexContext *lex = makeJsonLexContext(json, false); JsonTokenType tok; char *type; /* Lex exactly one token from the input and check its type. */ json_lex(lex); tok = lex_peek(lex); --- 2353,2367 ---- Datum json_typeof(PG_FUNCTION_ARGS) { ! text *json; ! 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 *** a/src/backend/utils/adt/jsonb.c --- b/src/backend/utils/adt/jsonb.c *************** *** 0 **** --- 1,461 ---- + /*------------------------------------------------------------------------- + * + * jsonb.c + * I/O routines for jsonb type + * + * Copyright (c) 2014, PostgreSQL Global Development Group + * + * src/backend/utils/adt/jsonb_support.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "libpq/pqformat.h" + #include "miscadmin.h" + #include "utils/builtins.h" + #include "utils/json.h" + #include "utils/jsonapi.h" + #include "utils/jsonb.h" + + typedef struct JsonbInState + { + ToJsonbState *state; + JsonbValue *res; + } JsonbInState; + + static inline Datum deserialize_json_text(char *json, int len); + static size_t checkStringLen(size_t len); + static void jsonb_in_object_start(void *state); + static void jsonb_in_object_end(void *state); + static void jsonb_in_array_start(void *state); + static void jsonb_in_array_end(void *state); + static void jsonb_in_object_field_start(void *state, char *fname, bool isnull); + static void jsonb_put_escaped_value(StringInfo out, JsonbValue * v); + static void jsonb_in_scalar(void *state, 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 deserialize_json_text(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 deserialize_json_text(str, nbytes); + } + + /* + * jsonb type output function + */ + Datum + jsonb_out(PG_FUNCTION_ARGS) + { + Jsonb *jb = PG_GETARG_JSONB(0); + char *out; + + out = JsonbToCString(NULL, (JB_ISEMPTY(jb)) ? NULL : VARDATA(jb), VARSIZE(jb)); + + PG_RETURN_CSTRING(out); + } + + /* + * jsonb type send function + * + * Just send jsonb as 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, (JB_ISEMPTY(jb)) ? NULL : 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) JsonbIteratorGet(&it, &v, true); + (void) JsonbIteratorGet(&it, &v, true); + switch (v.type) + { + case jbvNull: + result = "null"; + break; + case jbvString: + result = "string"; + break; + case jbvBool: + result = "boolean"; + break; + case jbvNumeric: + result = "number"; + break; + default: + elog(ERROR, "unknown jsonb scalar type"); + } + } + + PG_RETURN_TEXT_P(cstring_to_text(result)); + } + + /* + * deserialize_json_text + * + * turn json text into a jsonb Datum. + * + * uses the json parser with hooks to contruct the jsonb. + */ + static inline Datum + deserialize_json_text(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 > JSONB_MAX_STRING_LEN) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("string too long for jsonb string"))); + return len; + } + + static void + jsonb_in_object_start(void *state) + { + JsonbInState *_state = (JsonbInState *) state; + + _state->res = pushJsonbValue(&_state->state, WJB_BEGIN_OBJECT, NULL); + } + + static void + jsonb_in_object_end(void *state) + { + JsonbInState *_state = (JsonbInState *) state; + + _state->res = pushJsonbValue(&_state->state, WJB_END_OBJECT, NULL); + } + + static void + jsonb_in_array_start(void *state) + { + JsonbInState *_state = (JsonbInState *) state; + + _state->res = pushJsonbValue(&_state->state, WJB_BEGIN_ARRAY, NULL); + } + + static void + jsonb_in_array_end(void *state) + { + JsonbInState *_state = (JsonbInState *) state; + + _state->res = pushJsonbValue(&_state->state, WJB_END_ARRAY, NULL); + } + + static void + jsonb_in_object_field_start(void *state, char *fname, bool isnull) + { + JsonbInState *_state = (JsonbInState *) state; + JsonbValue v; + + v.type = jbvString; + v.string.len = fname ? checkStringLen(strlen(fname)) : 0; + v.string.val = fname ? pnstrdup(fname, v.string.len) : NULL; + v.size = sizeof(JEntry) + v.string.len; + + _state->res = pushJsonbValue(&_state->state, WJB_KEY, &v); + } + + static void + jsonb_put_escaped_value(StringInfo out, JsonbValue * v) + { + switch (v->type) + { + case jbvNull: + appendBinaryStringInfo(out, "null", 4); + break; + case jbvString: + escape_json(out, pnstrdup(v->string.val, v->string.len)); + break; + case jbvBool: + if (v->boolean) + appendBinaryStringInfo(out, "true", 4); + else + appendBinaryStringInfo(out, "false", 5); + break; + case jbvNumeric: + appendStringInfoString(out, + DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(v->numeric)))); + 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 *state, char *token, JsonTokenType tokentype) + { + JsonbInState *_state = (JsonbInState *) state; + JsonbValue v; + + v.size = sizeof(JEntry); + + switch (tokentype) + { + + case JSON_TOKEN_STRING: + v.type = jbvString; + v.string.len = token ? checkStringLen(strlen(token)) : 0; + v.string.val = token ? pnstrdup(token, v.string.len) : NULL; + v.size += v.string.len; + break; + case JSON_TOKEN_NUMBER: + v.type = jbvNumeric; + v.numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(token), 0, -1)); + + v.size += 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: + /* nothing else should be here in fact */ + Assert(false); + break; + } + + if (_state->state == NULL) + { + /* single scalar */ + JsonbValue va; + + va.type = jbvArray; + va.array.scalar = true; + va.array.nelems = 1; + + _state->res = pushJsonbValue(&_state->state, WJB_BEGIN_ARRAY, &va); + _state->res = pushJsonbValue(&_state->state, WJB_ELEM, &v); + _state->res = pushJsonbValue(&_state->state, WJB_END_ARRAY, NULL); + } + else + { + JsonbValue *o = &_state->state->v; + + switch (o->type) + { + case jbvArray: + _state->res = pushJsonbValue(&_state->state, WJB_ELEM, &v); + break; + case jbvObject: + _state->res = pushJsonbValue(&_state->state, WJB_VALUE, &v); + break; + default: + elog(ERROR, "unexpected parent of nested structure"); + } + } + } + + /* + * JsonbToCString + * Converts jsonb value in C-string. If out argument is not null + * then resulting C-string is placed in it. Return pointer to string. + * 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, char *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(); + + if (in == NULL) + { + appendStringInfoString(out, ""); + return out->data; + } + + enlargeStringInfo(out, (estimated_len >= 0) ? estimated_len : 64); + + it = JsonbIteratorInit(in); + + while (redo_switch || ((type = JsonbIteratorGet(&it, &v, false)) != 0)) + { + redo_switch = false; + switch (type) + { + case WJB_BEGIN_ARRAY: + if (first == false) + appendBinaryStringInfo(out, ", ", 2); + first = true; + + if (v.array.scalar == false) + appendStringInfoChar(out, '['); + level++; + break; + case WJB_BEGIN_OBJECT: + if (first == false) + appendBinaryStringInfo(out, ", ", 2); + first = true; + appendStringInfoCharMacro(out, '{'); + + level++; + break; + case WJB_KEY: + if (first == false) + appendBinaryStringInfo(out, ", ", 2); + first = true; + + /* json rules guarantee this is a string */ + jsonb_put_escaped_value(out, &v); + appendBinaryStringInfo(out, ": ", 2); + + type = JsonbIteratorGet(&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 == false) + appendBinaryStringInfo(out, ", ", 2); + else + first = false; + + jsonb_put_escaped_value(out, &v); + break; + case WJB_END_ARRAY: + level--; + if (v.array.scalar == false) + 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 *** a/src/backend/utils/adt/jsonb_gin.c --- b/src/backend/utils/adt/jsonb_gin.c *************** *** 0 **** --- 1,471 ---- + /*------------------------------------------------------------------------- + * + * 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_type.h" + #include "utils/builtins.h" + #include "utils/jsonb.h" + + #define PATH_SEPARATOR ("\0") + + typedef struct PathHashStack + { + pg_crc32 hash_state; + struct PathHashStack *next; + } PathHashStack; + + static text *makeitem(const char *str, int len, char flag); + static text *makeitemFromValue(const JsonbValue * v, char flag); + static void hash_stack_value(const JsonbValue * v, PathHashStack * stack); + + /* + * + * jsonb_ops GIN opclass support functions + * + */ + 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 = JsonbIteratorGet(&it, &v, false)) != 0) + { + if (i >= total) + { + total *= 2; + entries = (Datum *) repalloc(entries, sizeof(Datum) * total); + } + + switch (r) + { + case WJB_KEY: + /* + * Serialize keys and elements as one. This is necessary + * because array elements must be indexed as keys for the + * benefit of JsonbContainsStrategyNumber. + */ + case WJB_ELEM: + entries[i++] = PointerGetDatum(makeitemFromValue(&v, KEYELEMFLAG)); + break; + case WJB_VALUE: + entries[i++] = PointerGetDatum(makeitemFromValue(&v, VALFLAG)); + break; + default: + break; + } + } + + *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 = makeitem(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query), + KEYELEMFLAG); + 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 = makeitem(VARDATA(key_datums[i]), + VARSIZE(key_datums[i]) - VARHDRSZ, + KEYELEMFLAG); + 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 keys and + * values, so invariably we recheck. 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); + } + + /* + * + * 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) + { + /* + * jsonb_hash_ops index doesn't have information about correspondence + * of keys and values, so invariably we recheck. 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 + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_BOOL(res); + } + + Datum + gin_extract_jsonb_hash(PG_FUNCTION_ARGS) + { + Jsonb *jb = 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; + PathHashStack tail; + PathHashStack *stack, + *tmp; + pg_crc32 path_crc32; + + if (total == 0) + { + *nentries = 0; + PG_RETURN_POINTER(NULL); + } + + entries = (Datum *) palloc(sizeof(Datum) * total); + + it = JsonbIteratorInit(VARDATA(jb)); + + tail.next = NULL; + INIT_CRC32(tail.hash_state); + stack = &tail; + + /* + * Calculate hashes of all key_1.key_2. ... .key_n.value paths as entries. + * The order of array elements doesn't matter, so array keys are empty in + * path. For faster calculation of hashes, use a stack of precalculated + * hashes of prefixes. + */ + while ((r = JsonbIteratorGet(&it, &v, false)) != 0) + { + if (i >= total) + { + total *= 2; + entries = (Datum *) repalloc(entries, sizeof(Datum) * total); + } + + switch (r) + { + case WJB_BEGIN_ARRAY: + tmp = stack; + stack = (PathHashStack *) palloc(sizeof(PathHashStack)); + stack->next = tmp; + stack->hash_state = tmp->hash_state; + COMP_CRC32(stack->hash_state, PATH_SEPARATOR, 1); + break; + case WJB_BEGIN_OBJECT: + /* Preserve stack item for key */ + tmp = stack; + stack = (PathHashStack *) palloc(sizeof(PathHashStack)); + stack->next = tmp; + break; + case WJB_KEY: + /* Calc hash of key and separated into preserved stack item */ + stack->hash_state = stack->next->hash_state; + hash_stack_value(&v, stack); + COMP_CRC32(stack->hash_state, PATH_SEPARATOR, 1); + break; + case WJB_VALUE: + case WJB_ELEM: + hash_stack_value(&v, stack); + path_crc32 = stack->hash_state; + FIN_CRC32(path_crc32); + entries[i++] = path_crc32; + break; + case WJB_END_ARRAY: + case WJB_END_OBJECT: + /* Pop stack item */ + tmp = stack->next; + pfree(stack); + stack = tmp; + break; + default: + break; + } + } + + *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) + { + /* 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; + } + else + { + elog(ERROR, "unrecognized strategy number: %d", strategy); + entries = NULL; /* keep compiler quiet */ + } + + PG_RETURN_POINTER(entries); + } + + /* + * Build an indexable text value from a cstring and flag + */ + static text * + makeitem(const char *str, int len, char flag) + { + text *item; + + item = (text *) palloc(VARHDRSZ + len + 1); + SET_VARSIZE(item, VARHDRSZ + len + 1); + + *VARDATA(item) = flag; + + if (str && len > 0) + memcpy(VARDATA(item) + 1, str, len); + + return item; + } + + /* + * Create a textual representation of a jsonbValue for GIN storage. + */ + static text * + makeitemFromValue(const JsonbValue * v, char flag) + { + text *item; + char *cstr; + + switch (v->type) + { + case jbvNull: + item = makeitem(NULL, 0, NULLFLAG); + break; + case jbvBool: + item = makeitem((v->boolean) ? " t" : " f", 2, flag); + break; + case jbvNumeric: + /* + * A normalized textual representation, free of trailing zeroes is + * is required. + */ + cstr = numeric_normalize(v->numeric); + item = makeitem(cstr, strlen(cstr), flag); + pfree(cstr); + break; + case jbvString: + item = makeitem(v->string.val, v->string.len, flag); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + + return item; + } + + /* + * Hash a JsonbValue, and push it on to our stack + */ + static void + hash_stack_value(const JsonbValue * v, PathHashStack * stack) + { + switch (v->type) + { + case jbvNull: + COMP_CRC32(stack->hash_state, "n", 2); + break; + case jbvBool: + COMP_CRC32(stack->hash_state, (v->boolean) ? " t" : " f", 2); + break; + case jbvNumeric: + /* + * A hash value unaffected by trailing zeroes is required. + */ + stack->hash_state ^= DatumGetInt32(DirectFunctionCall1(hash_numeric, + NumericGetDatum(v->numeric))); + break; + case jbvString: + COMP_CRC32(stack->hash_state, v->string.val, v->string.len); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + break; + } + } diff --git a/src/backend/utils/adt/jsonb_gist.c b/src/backend/utils/adt/jsonb_gist.c new file mode 100644 *** a/src/backend/utils/adt/jsonb_gist.c --- b/src/backend/utils/adt/jsonb_gist.c *************** *** 0 **** --- 1,699 ---- + /*------------------------------------------------------------------------- + * + * jsonb_gist.c + * GiST support functions for jsonb + * + * Copyright (c) 2014, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/utils/adt/jsonb_gist.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/gist.h" + #include "access/skey.h" + #include "catalog/pg_type.h" + #include "utils/builtins.h" + #include "utils/jsonb.h" + #include "utils/pg_crc.h" + + /* bigint defines */ + #define BITBYTE 8 + #define SIGLENINT 4 /* > 122 => key be will toasted */ + #define SIGLEN (sizeof(int)*SIGLENINT) + #define SIGLENBIT (SIGLEN*BITBYTE) + + typedef char BITVEC[SIGLEN]; + typedef char *BITVECP; + + #define SIGPTR(x) ( (BITVECP) ARR_DATA_PTR(x) ) + + #define LOOPBYTE \ + for(i=0;i> (i)) & 0x01) + #define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITBYTE )) + #define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITBYTE )) + #define GETBIT(x,i) ((GETBYTE(x,i) >> ( (i) % BITBYTE )) & 0x01) + #define HASHVAL(val) (((unsigned int)(val)) % SIGLENBIT) + #define HASH(sign, val) SETBIT((sign), HASHVAL(val)) + + #define ALLISTRUE 0x04 + #define ISALLTRUE(x) (((GISTTYPE*)x)->flag & ALLISTRUE) + #define GTHDRSIZE (VARHDRSZ + sizeof(int32)) + #define CALCGTSIZE(flag) (GTHDRSIZE+(((flag) & ALLISTRUE) ? 0 : SIGLEN)) + #define GETSIGN(x) ((BITVECP)( (char*)x+GTHDRSIZE)) + #define SUMBIT(val) ( \ + GETBITBYTE((val),0) + \ + GETBITBYTE((val),1) + \ + GETBITBYTE((val),2) + \ + GETBITBYTE((val),3) + \ + GETBITBYTE((val),4) + \ + GETBITBYTE((val),5) + \ + GETBITBYTE((val),6) + \ + GETBITBYTE((val),7) \ + ) + + #define GETENTRY(vec,pos) ((GISTTYPE *) DatumGetPointer((vec)->vector[(pos)].key)) + #define WISH_F(a,b,c) ((double) ( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c))) + + typedef struct + { + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 flag; + char data[1]; + } GISTTYPE; + + typedef struct + { + OffsetNumber pos; + int32 cost; + } SPLITCOST; + + static int crc32_JsonbValue(JsonbValue * v, uint32 r); + static int crc32_Key(char *buf, int sz); + static int32 sizebitvec(BITVECP sign); + static int hemdistsign(BITVECP a, BITVECP b); + static int hemdist(GISTTYPE *a, GISTTYPE *b); + static int32 unionkey(BITVECP sbase, GISTTYPE *add); + static int comparecost(const void *a, const void *b); + + Datum + gjsonb_in(PG_FUNCTION_ARGS) + { + elog(ERROR, "not implemented"); + PG_RETURN_DATUM(0); + } + + Datum + gjsonb_out(PG_FUNCTION_ARGS) + { + elog(ERROR, "not implemented"); + PG_RETURN_DATUM(0); + } + + Datum + gjsonb_consistent(PG_FUNCTION_ARGS) + { + GISTTYPE *entry = (GISTTYPE *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool res = true; + BITVECP sign; + + /* All cases served by this function are inexact */ + *recheck = true; + + if (ISALLTRUE(entry)) + PG_RETURN_BOOL(true); + + sign = GETSIGN(entry); + + if (strategy == JsonbContainsStrategyNumber) + { + BITVECP qe; + int i; + + qe = fcinfo->flinfo->fn_extra; + if (qe == NULL) + { + Jsonb *query = PG_GETARG_JSONB(1); + + qe = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(BITVEC)); + memset(qe, 0, sizeof(BITVEC)); + + if (!JB_ISEMPTY(query)) + { + int r; + JsonbIterator *it = JsonbIteratorInit(VARDATA(query)); + JsonbValue v; + + while ((r = JsonbIteratorGet(&it, &v, false)) != 0) + { + if ((r == WJB_ELEM || r == WJB_KEY || r == WJB_VALUE) && v.type != jbvNull) + { + int crc = crc32_JsonbValue(&v, r); + + HASH(qe, crc); + } + } + } + + fcinfo->flinfo->fn_extra = qe; + } + + LOOPBYTE + { + if ((sign[i] & qe[i]) != qe[i]) + { + res = false; + break; + } + } + } + else if (strategy == JsonbExistsStrategyNumber) + { + int *qval; + + qval = fcinfo->flinfo->fn_extra; + if (qval == NULL) + { + text *query = PG_GETARG_TEXT_PP(1); + int crc = crc32_Key(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query)); + + qval = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(int)); + *qval = HASHVAL(crc); + + fcinfo->flinfo->fn_extra = qval; + } + + res = (GETBIT(sign, *qval)) != 0; + } + else if (strategy == JsonbExistsAllStrategyNumber || + strategy == JsonbExistsAnyStrategyNumber) + { + BITVECP arrentry; + int i; + + arrentry = fcinfo->flinfo->fn_extra; + if (arrentry == NULL) + { + ArrayType *query = PG_GETARG_ARRAYTYPE_P(1); + Datum *key_datums; + bool *key_nulls; + int key_count; + + arrentry = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(BITVEC)); + memset(arrentry, 0, sizeof(BITVEC)); + + deconstruct_array(query, + TEXTOID, -1, false, 'i', + &key_datums, &key_nulls, &key_count); + + for (i = 0; i < key_count; ++i) + { + int crc; + + if (key_nulls[i]) + continue; + crc = crc32_Key(VARDATA(key_datums[i]), + VARSIZE(key_datums[i]) - VARHDRSZ); + HASH(arrentry, crc); + } + + fcinfo->flinfo->fn_extra = arrentry; + } + + if (strategy == JsonbExistsAllStrategyNumber) + { + LOOPBYTE + { + if ((sign[i] & arrentry[i]) != arrentry[i]) + { + res = false; + break; + } + } + } + else /* JsonbExistsAnyStrategyNumber */ + { + res = false; + + LOOPBYTE + { + if (sign[i] & arrentry[i]) + { + res = true; + break; + } + } + } + } + else + elog(ERROR, "unsupported strategy number: %d", strategy); + + PG_RETURN_BOOL(res); + } + + Datum + gjsonb_union(PG_FUNCTION_ARGS) + { + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 len = entryvec->n; + + int *size = (int *) PG_GETARG_POINTER(1); + BITVEC base; + int32 i; + int32 flag = 0; + GISTTYPE *result; + + MemSet((void *) base, 0, sizeof(BITVEC)); + for (i = 0; i < len; i++) + { + if (unionkey(base, GETENTRY(entryvec, i))) + { + flag = ALLISTRUE; + break; + } + } + + len = CALCGTSIZE(flag); + result = (GISTTYPE *) palloc(len); + SET_VARSIZE(result, len); + result->flag = flag; + if (!ISALLTRUE(result)) + memcpy((void *) GETSIGN(result), (void *) base, sizeof(BITVEC)); + *size = len; + + PG_RETURN_POINTER(result); + } + + Datum + gjsonb_compress(PG_FUNCTION_ARGS) + { + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { + GISTTYPE *res = (GISTTYPE *) palloc0(CALCGTSIZE(0)); + Jsonb *val = (Jsonb *) PG_DETOAST_DATUM(entry->key); + + SET_VARSIZE(res, CALCGTSIZE(0)); + + if (!JB_ISEMPTY(val)) + { + JsonbIterator *it = JsonbIteratorInit(VARDATA(val)); + JsonbValue v; + int r; + + while ((r = JsonbIteratorGet(&it, &v, false)) != 0) + { + if ((r == WJB_ELEM || r == WJB_KEY || r == WJB_VALUE) && + v.type != jbvNull) + { + int h = crc32_JsonbValue(&v, r); + + HASH(GETSIGN(res), h); + } + } + } + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, + FALSE); + } + else if (!ISALLTRUE(DatumGetPointer(entry->key))) + { + int32 i; + GISTTYPE *res; + BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); + + LOOPBYTE + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(retval); + } + + res = (GISTTYPE *) palloc(CALCGTSIZE(ALLISTRUE)); + SET_VARSIZE(res, CALCGTSIZE(ALLISTRUE)); + res->flag = ALLISTRUE; + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, + FALSE); + } + + PG_RETURN_POINTER(retval); + } + + /* + * Since type gjsonb isn't toastable (and doesn't need to be), + * this function can be a no-op. + */ + Datum + gjsonb_decompress(PG_FUNCTION_ARGS) + { + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); + } + + Datum + gjsonb_penalty(PG_FUNCTION_ARGS) + { + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + GISTTYPE *origval = (GISTTYPE *) DatumGetPointer(origentry->key); + GISTTYPE *newval = (GISTTYPE *) DatumGetPointer(newentry->key); + + *penalty = hemdist(origval, newval); + PG_RETURN_POINTER(penalty); + } + + Datum + gjsonb_picksplit(PG_FUNCTION_ARGS) + { + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + OffsetNumber maxoff = entryvec->n - 2; + + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber k, + j; + GISTTYPE *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + BITVECP ptr; + int i; + SPLITCOST *costvector; + GISTTYPE *_k, + *_j; + + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + _k = GETENTRY(entryvec, k); + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdist(_k, GETENTRY(entryvec, j)); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* form initial .. */ + if (ISALLTRUE(GETENTRY(entryvec, seed_1))) + { + datum_l = (GISTTYPE *) palloc(GTHDRSIZE); + SET_VARSIZE(datum_l, GTHDRSIZE); + datum_l->flag = ALLISTRUE; + } + else + { + datum_l = (GISTTYPE *) palloc(GTHDRSIZE + SIGLEN); + SET_VARSIZE(datum_l, GTHDRSIZE + SIGLEN); + datum_l->flag = 0; + memcpy((void *) GETSIGN(datum_l), + (void *) GETSIGN(GETENTRY(entryvec, seed_1)), sizeof(BITVEC)); + } + if (ISALLTRUE(GETENTRY(entryvec, seed_2))) + { + datum_r = (GISTTYPE *) palloc(GTHDRSIZE); + SET_VARSIZE(datum_r, GTHDRSIZE); + datum_r->flag = ALLISTRUE; + } + else + { + datum_r = (GISTTYPE *) palloc(GTHDRSIZE + SIGLEN); + SET_VARSIZE(datum_r, GTHDRSIZE + SIGLEN); + datum_r->flag = 0; + memcpy((void *) GETSIGN(datum_r), + (void *) GETSIGN(GETENTRY(entryvec, seed_2)), sizeof(BITVEC)); + } + + maxoff = OffsetNumberNext(maxoff); + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j); + size_beta = hemdist(datum_r, _j); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort((void *) costvector, maxoff, sizeof(SPLITCOST), comparecost); + + union_l = GETSIGN(datum_l); + union_r = GETSIGN(datum_r); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j); + size_beta = hemdist(datum_r, _j); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.0001)) + { + if (ISALLTRUE(datum_l) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_l)) + MemSet((void *) union_l, 0xff, sizeof(BITVEC)); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (ISALLTRUE(datum_r) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_r)) + MemSet((void *) union_r, 0xff, sizeof(BITVEC)); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); + } + + Datum + gjsonb_same(PG_FUNCTION_ARGS) + { + GISTTYPE *a = (GISTTYPE *) PG_GETARG_POINTER(0); + GISTTYPE *b = (GISTTYPE *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + if (ISALLTRUE(a) && ISALLTRUE(b)) + *result = true; + else if (ISALLTRUE(a)) + *result = false; + else if (ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = GETSIGN(a), + sb = GETSIGN(b); + + *result = true; + LOOPBYTE + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + PG_RETURN_POINTER(result); + } + + static int + crc32_JsonbValue(JsonbValue * v, uint32 r) + { + int crc; + char flag = '\0'; + + INIT_CRC32(crc); + + switch (r) + { + case WJB_KEY: + /* + * Serialize keys and elements as one. This is necessary because + * array elements must be indexed as keys for the benefit of + * JsonbContainsStrategyNumber. + */ + case WJB_ELEM: + flag = KEYELEMFLAG; + break; + case WJB_VALUE: + flag = VALFLAG; + break; + default: + break; + } + + COMP_CRC32(crc, &flag, 1); + + switch (v->type) + { + case jbvString: + COMP_CRC32(crc, v->string.val, v->string.len); + break; + case jbvBool: + flag = v->boolean ? 't' : 'f'; + COMP_CRC32(crc, &flag, 1); + break; + case jbvNumeric: + /* + * A hash value unaffected by trailing zeros is required. + */ + crc = DatumGetInt32(DirectFunctionCall1(hash_numeric, + NumericGetDatum(v->numeric))); + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + + FIN_CRC32(crc); + return crc; + } + + static int + crc32_Key(char *buf, int sz) + { + int crc; + char flag = KEYELEMFLAG; + + INIT_CRC32(crc); + + COMP_CRC32(crc, &flag, 1); + COMP_CRC32(crc, buf, sz); + + FIN_CRC32(crc); + return crc; + } + + static int32 + sizebitvec(BITVECP sign) + { + int32 size = 0, + i; + + LOOPBYTE + { + size += SUMBIT(sign); + sign = (BITVECP) (((char *) sign) + 1); + } + + return size; + } + + static int + hemdistsign(BITVECP a, BITVECP b) + { + int i, + dist = 0; + + LOOPBIT + { + if (GETBIT(a, i) != GETBIT(b, i)) + dist++; + } + + return dist; + } + + static int + hemdist(GISTTYPE *a, GISTTYPE *b) + { + if (ISALLTRUE(a)) + { + if (ISALLTRUE(b)) + return 0; + else + return SIGLENBIT - sizebitvec(GETSIGN(b)); + } + else if (ISALLTRUE(b)) + { + return SIGLENBIT - sizebitvec(GETSIGN(a)); + } + + return hemdistsign(GETSIGN(a), GETSIGN(b)); + } + + static int32 + unionkey(BITVECP sbase, GISTTYPE *add) + { + int32 i; + BITVECP sadd = GETSIGN(add); + + if (ISALLTRUE(add)) + return 1; + + LOOPBYTE + sbase[i] |= sadd[i]; + + return 0; + } + + static int + comparecost(const void *a, const void *b) + { + return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; + } diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c new file mode 100644 *** a/src/backend/utils/adt/jsonb_op.c --- b/src/backend/utils/adt/jsonb_op.c *************** *** 0 **** --- 1,565 ---- + /*------------------------------------------------------------------------- + * + * 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 "access/hash.h" + #include "catalog/pg_type.h" + #include "funcapi.h" + #include "utils/builtins.h" + #include "utils/jsonb.h" + #include "utils/memutils.h" + #include "utils/pg_crc.h" + + static bool deepContains(JsonbIterator ** it1, JsonbIterator ** it2); + static JsonbValue *arrayToJsonbSortedArray(ArrayType *a); + + Datum + jsonb_exists(PG_FUNCTION_ARGS) + { + Jsonb *jb = PG_GETARG_JSONB(0); + text *key = PG_GETARG_TEXT_PP(1); + JsonbValue *v = NULL; + + if (!JB_ISEMPTY(jb)) + v = findUncompressedJsonbValue(VARDATA(jb), + JB_FLAG_OBJECT | JB_FLAG_ARRAY, + NULL, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key)); + + 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 *v = arrayToJsonbSortedArray(keys); + int i; + uint32 *plowbound = NULL, + lowbound = 0; + bool res = false; + + if (JB_ISEMPTY(jb) || v == NULL || v->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 findUncompressedJsonbValue 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 < v->array.nelems; i++) + { + if (findUncompressedJsonbValueByValue(VARDATA(jb), + JB_FLAG_OBJECT | JB_FLAG_ARRAY, + plowbound, + v->array.elems + i) != NULL) + { + res = true; + break; + } + } + + PG_RETURN_BOOL(res); + } + + Datum + jsonb_exists_all(PG_FUNCTION_ARGS) + { + Jsonb *js = PG_GETARG_JSONB(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + JsonbValue *v = arrayToJsonbSortedArray(keys); + uint32 *plowbound = NULL; + uint32 lowbound = 0; + bool res = true; + int i; + + if (JB_ISEMPTY(js) || v == NULL || v->array.nelems == 0) + { + if (v == NULL || v->array.nelems == 0) + PG_RETURN_BOOL(true); /* compatibility */ + else + PG_RETURN_BOOL(false); + } + + if (JB_ROOT_IS_OBJECT(js)) + plowbound = &lowbound; + + /* + * We exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the findUncompressedJsonbValue 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 < v->array.nelems; i++) + { + if (findUncompressedJsonbValueByValue(VARDATA(js), + JB_FLAG_OBJECT | JB_FLAG_ARRAY, + plowbound, + v->array.elems + i) == NULL) + { + res = false; + break; + } + } + + PG_RETURN_BOOL(res); + } + + Datum + jsonb_contains(PG_FUNCTION_ARGS) + { + Jsonb *val = PG_GETARG_JSONB(0); + Jsonb *tmpl = PG_GETARG_JSONB(1); + + bool res = true; + 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)); + res = deepContains(&it1, &it2); + + PG_RETURN_BOOL(res); + } + + Datum + jsonb_contained(PG_FUNCTION_ARGS) + { + PG_RETURN_DATUM(DirectFunctionCall2(jsonb_contains, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); + } + + Datum + jsonb_ne(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res != 0); + } + + /* + * B-Tree operator class operators, support function + */ + Datum + jsonb_lt(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res < 0); + } + + Datum + jsonb_gt(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res > 0); + } + + Datum + jsonb_le(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res <= 0); + } + + Datum + jsonb_ge(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res >= 0); + } + + Datum + jsonb_eq(PG_FUNCTION_ARGS) + { + int res = DatumGetInt32(DirectFunctionCall2(jsonb_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res == 0); + } + + Datum + jsonb_cmp(PG_FUNCTION_ARGS) + { + Jsonb *jb1 = PG_GETARG_JSONB(0); + Jsonb *jb2 = PG_GETARG_JSONB(1); + + int res; + + if (JB_ISEMPTY(jb1) || JB_ISEMPTY(jb2)) + { + if (JB_ISEMPTY(jb1)) + { + if (JB_ISEMPTY(jb2)) + res = 0; + else + res = -1; + } + else + { + res = 1; + } + } + else if (JB_ROOT_IS_SCALAR(jb1) && ! JB_ROOT_IS_SCALAR(jb2)) + { + res = -1; + } + else if (JB_ROOT_IS_SCALAR(jb2) && ! JB_ROOT_IS_SCALAR(jb1)) + { + res = 1; + } + else + { + res = compareJsonbBinaryValue(VARDATA(jb1), VARDATA(jb2)); + } + + /* + * This is a btree support function; this is one of the few places where + * memory needs to be explicitly freed. + */ + PG_FREE_IF_COPY(jb1, 0); + PG_FREE_IF_COPY(jb2, 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; + int crc; + + if (JB_ROOT_COUNT(jb) == 0) + PG_RETURN_INT32(0x1EEE); + + it = JsonbIteratorInit(VARDATA(jb)); + INIT_CRC32(crc); + + while ((r = JsonbIteratorGet(&it, &v, false)) != 0) + { + switch (r) + { + case WJB_BEGIN_ARRAY: + COMP_CRC32(crc, "ab", 3); + COMP_CRC32(crc, &v.array.nelems, sizeof(v.array.nelems)); + COMP_CRC32(crc, &v.array.scalar, sizeof(v.array.scalar)); + break; + case WJB_BEGIN_OBJECT: + COMP_CRC32(crc, "hb", 3); + COMP_CRC32(crc, &v.object.npairs, sizeof(v.object.npairs)); + break; + case WJB_KEY: + COMP_CRC32(crc, "k", 2); + case WJB_VALUE: + case WJB_ELEM: + switch (v.type) + { + case jbvString: + COMP_CRC32(crc, v.string.val, v.string.len); + break; + case jbvNull: + COMP_CRC32(crc, "N", 2); + break; + case jbvBool: + COMP_CRC32(crc, &v.boolean, sizeof(v.boolean)); + break; + case jbvNumeric: + crc ^= DatumGetInt32(DirectFunctionCall1(hash_numeric, + NumericGetDatum(v.numeric))); + break; + default: + elog(ERROR, "invalid jsonb iterator type"); + } + break; + case WJB_END_ARRAY: + COMP_CRC32(crc, "ae", 3); + break; + case WJB_END_OBJECT: + COMP_CRC32(crc, "he", 3); + break; + default: + elog(ERROR, "invalid jsonb iterator type"); + } + } + + FIN_CRC32(crc); + + PG_FREE_IF_COPY(jb, 0); + PG_RETURN_INT32(crc); + } + + static bool + deepContains(JsonbIterator ** it1, JsonbIterator ** it2) + { + uint32 r1, + r2; + JsonbValue v1, + v2; + bool res = true; + + r1 = JsonbIteratorGet(it1, &v1, false); + r2 = JsonbIteratorGet(it2, &v2, false); + + if (r1 != r2) + { + res = false; + } + else if (r1 == WJB_BEGIN_OBJECT) + { + uint32 lowbound = 0; + JsonbValue *v; + + for (;;) + { + r2 = JsonbIteratorGet(it2, &v2, false); + if (r2 == WJB_END_OBJECT) + break; + + Assert(r2 == WJB_KEY); + + v = findUncompressedJsonbValueByValue((*it1)->buffer, + JB_FLAG_OBJECT, + &lowbound, &v2); + + if (v == NULL) + { + res = false; + break; + } + + r2 = JsonbIteratorGet(it2, &v2, true); + Assert(r2 == WJB_VALUE); + + if (v->type != v2.type) + { + res = false; + break; + } + else if (v->type == jbvString || v->type == jbvNull || + v->type == jbvBool || v->type == jbvNumeric) + { + if (compareJsonbValue(v, &v2) != 0) + { + res = false; + break; + } + } + else + { + JsonbIterator *it1a, + *it2a; + + Assert(v2.type == jbvBinary); + Assert(v->type == jbvBinary); + + it1a = JsonbIteratorInit(v->binary.data); + it2a = JsonbIteratorInit(v2.binary.data); + + if ((res = deepContains(&it1a, &it2a)) == false) + break; + } + } + } + else if (r1 == WJB_BEGIN_ARRAY) + { + JsonbValue *v; + JsonbValue *av = NULL; + uint32 nelems = v1.array.nelems; + + for (;;) + { + r2 = JsonbIteratorGet(it2, &v2, true); + if (r2 == WJB_END_ARRAY) + break; + + Assert(r2 == WJB_ELEM); + + if (v2.type == jbvString || v2.type == jbvNull || + v2.type == jbvBool || v2.type == jbvNumeric) + { + v = findUncompressedJsonbValueByValue((*it1)->buffer, + JB_FLAG_ARRAY, NULL, + &v2); + if (v == NULL) + { + res = false; + break; + } + } + else + { + uint32 i; + + if (av == NULL) + { + uint32 j = 0; + + av = palloc(sizeof(JsonbValue) * nelems); + + for (i = 0; i < nelems; i++) + { + r2 = JsonbIteratorGet(it1, &v1, true); + Assert(r2 == WJB_ELEM); + + if (v1.type == jbvBinary) + av[j++] = v1; + } + + if (j == 0) + { + res = false; + break; + } + + nelems = j; + } + + res = false; + for (i = 0; res == false && i < nelems; i++) + { + JsonbIterator *it1a, + *it2a; + + it1a = JsonbIteratorInit(av[i].binary.data); + it2a = JsonbIteratorInit(v2.binary.data); + + res = deepContains(&it1a, &it2a); + } + + if (res == false) + break; + } + } + } + else + { + elog(ERROR, "invalid jsonb container type"); + } + + return res; + } + + /* + * Convert a Postgres text array to a JsonbSortedArray, with de-duplicated key + * elements. + */ + static JsonbValue * + arrayToJsonbSortedArray(ArrayType *a) + { + Datum *key_datums; + bool *key_nulls; + int key_count; + JsonbValue *result; + int i, + j; + bool hasNonUniq = false; + + /* Extract data for sorting */ + deconstruct_array(a, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, + &key_count); + + if (key_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 (key_count > MaxAllocSize / sizeof(JsonbPair)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%zu)", + key_count, MaxAllocSize / sizeof(JsonbPair)))); + + result = palloc(sizeof(JsonbValue)); + result->type = jbvArray; + result->array.scalar = false; + result->array.elems = palloc(sizeof(*result->object.pairs) * key_count); + + for (i = 0, j = 0; i < key_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; + + /* + * Actually sort values, determining if any were equal on the basis of full + * binary equality (rather than just having the same string length). + */ + if (result->array.nelems > 1) + qsort_arg(result->array.elems, result->array.nelems, + sizeof(*result->array.elems), compareJsonbStringValue, + &hasNonUniq); + + if (hasNonUniq) + { + JsonbValue *ptr = result->array.elems + 1, + *res = result->array.elems; + + while (ptr - result->array.elems < result->array.nelems) + { + /* Avoid copying over binary duplicate */ + if (!(ptr->string.len == res->string.len && + memcmp(ptr->string.val, res->string.val, ptr->string.len) == 0)) + { + res++; + *res = *ptr; + } + + ptr++; + } + + result->array.nelems = res + 1 - result->array.elems; + } + + return result; + } diff --git a/src/backend/utils/adt/jsonb_support.c b/src/backend/utils/adt/jsonb_support.c new file mode 100644 *** a/src/backend/utils/adt/jsonb_support.c --- b/src/backend/utils/adt/jsonb_support.c *************** *** 0 **** --- 1,1350 ---- + /*------------------------------------------------------------------------- + * + * jsonb_support.c + * Support functions for jsonb + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * + * src/backend/utils/adt/jsonb_support.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "miscadmin.h" + #include "catalog/pg_collation.h" + #include "utils/builtins.h" + #include "utils/jsonb.h" + + #define JENTRY_ISFIRST (0x80000000) + #define JENTRY_ISSTRING (0x00000000) + #define JENTRY_ISNUMERIC (0x10000000) + #define JENTRY_ISNEST (0x20000000) + #define JENTRY_ISNULL (0x40000000) + #define JENTRY_ISBOOL (0x10000000 | 0x20000000) + #define JENTRY_ISFALSE JENTRY_ISBOOL + #define JENTRY_ISTRUE (0x10000000 | 0x20000000 | 0x40000000) + + /* Note possible multiple evaluations, also access to prior array element */ + #define JBE_ISFIRST(he_) (((he_).entry & JENTRY_ISFIRST) != 0) + #define JBE_ISSTRING(he_) (((he_).entry & JENTRY_TYPEMASK) == JENTRY_ISSTRING) + #define JBE_ISNUMERIC(he_) (((he_).entry & JENTRY_TYPEMASK) == JENTRY_ISNUMERIC) + #define JBE_ISNEST(he_) (((he_).entry & JENTRY_TYPEMASK) == JENTRY_ISNEST) + #define JBE_ISNULL(he_) (((he_).entry & JENTRY_TYPEMASK) == JENTRY_ISNULL) + #define JBE_ISBOOL(he_) (((he_).entry & JENTRY_TYPEMASK & JENTRY_ISBOOL) == JENTRY_ISBOOL) + #define JBE_ISBOOL_TRUE(he_) (((he_).entry & JENTRY_TYPEMASK) == JENTRY_ISTRUE) + #define JBE_ISBOOL_FALSE(he_) (JBE_ISBOOL(he_) && !JBE_ISBOOL_TRUE(he_)) + + #define JBE_ENDPOS(he_) ((he_).entry & JENTRY_POSMASK) + #define JBE_OFF(he_) (JBE_ISFIRST(he_) ? 0 : JBE_ENDPOS((&(he_))[-1])) + #define JBE_LEN(he_) (JBE_ISFIRST(he_) \ + ? JBE_ENDPOS(he_) \ + : JBE_ENDPOS(he_) - JBE_ENDPOS((&(he_))[-1])) + + typedef void (*walk_jsonb_cb) (void * /* arg */ , JsonbValue * /* value */ , + uint32 /* flags */ , uint32 /* level */ ); + + typedef struct CompressState + { + char *begin; + char *ptr; + + struct + { + uint32 i; + uint32 *header; + JEntry *array; + char *begin; + } *levelstate, *lptr, *pptr; + + uint32 maxlevel; + + } CompressState; + + static void walkUncompressedJsonbDo(JsonbValue * v, walk_jsonb_cb cb, + void *cb_arg, uint32 level); + static void walkUncompressedJsonb(JsonbValue * v, walk_jsonb_cb cb, + void *cb_arg); + static void parseBuffer(JsonbIterator * it, char *buffer); + static bool formAnswer(JsonbIterator ** it, JsonbValue * v, JEntry * e, + bool skipNested); + static JsonbIterator *up(JsonbIterator * it); + static void putJEntryString(CompressState * state, JsonbValue * value, + uint32 level, uint32 i); + static void compressCallback(void *arg, JsonbValue * value, uint32 flags, + uint32 level); + static uint32 compressJsonb(JsonbValue * v, char *buffer); + static ToJsonbState *pushState(ToJsonbState ** state); + static void appendArray(ToJsonbState * state, JsonbValue * v); + static void appendKey(ToJsonbState * state, JsonbValue * v); + static void appendValue(ToJsonbState * state, JsonbValue * v); + static int compareJsonbPair(const void *a, const void *b, void *arg); + static void uniqueifyJsonbValue(JsonbValue * v); + + /* + * Turn a JsonbValue into a Jsonb + */ + Jsonb * + JsonbValueToJsonb(JsonbValue * v) + { + Jsonb *out; + + if (v == NULL) + { + out = NULL; + } + else if (v->type == jbvString || v->type == jbvBool || + v->type == jbvNumeric || v->type == jbvNull) + { + /* Scalar value */ + ToJsonbState *state = NULL; + JsonbValue *res; + uint32 sz; + JsonbValue scalarArray; + + scalarArray.type = jbvArray; + scalarArray.array.scalar = true; + scalarArray.array.nelems = 1; + + pushJsonbValue(&state, WJB_BEGIN_ARRAY, &scalarArray); + pushJsonbValue(&state, WJB_ELEM, v); + res = pushJsonbValue(&state, WJB_END_ARRAY, NULL); + + out = palloc(VARHDRSZ + res->size); + sz = compressJsonb(res, VARDATA(out)); + Assert(sz <= res->size); + SET_VARSIZE(out, sz + VARHDRSZ); + } + else if (v->type == jbvObject || v->type == jbvArray) + { + uint32 sz; + + out = palloc(VARHDRSZ + v->size); + sz = compressJsonb(v, VARDATA(out)); + Assert(sz <= v->size); + SET_VARSIZE(out, VARHDRSZ + sz); + } + else + { + out = palloc(VARHDRSZ + v->binary.len); + + Assert(v->type == jbvBinary); + SET_VARSIZE(out, VARHDRSZ + v->binary.len); + memcpy(VARDATA(out), v->binary.data, v->binary.len); + } + + return out; + } + + /**************************************************************************** + * Internal comparison functions * + ****************************************************************************/ + + /* + * 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. + * + * 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. + */ + int + compareJsonbStringValue(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; + } + + /* + * Standard lexical qsort() comparator of jsonb strings. + * + * Sorts strings lexically, using the default text collation. Used by B-Tree + * operators. + */ + 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); + } + + /* + * Compare 2 JsonbValues + */ + int + compareJsonbValue(JsonbValue * a, JsonbValue * b) + { + check_stack_depth(); + + if (a->type == b->type) + { + switch (a->type) + { + case jbvNull: + return 0; + case jbvString: + return lexicalCompareJsonbStringValue(a, b); + case jbvBool: + if (a->boolean == b->boolean) + return 0; + return (a->boolean > b->boolean) ? 1 : -1; + case jbvNumeric: + return DatumGetInt32(DirectFunctionCall2(numeric_cmp, + PointerGetDatum(a->numeric), + PointerGetDatum(b->numeric))); + case jbvArray: + if (a->array.nelems == b->array.nelems) + { + int i, + r; + + for (i = 0; i < a->array.nelems; i++) + if ((r = compareJsonbValue(a->array.elems + i, + b->array.elems + i)) != 0) + return r; + + return 0; + } + + return (a->array.nelems > b->array.nelems) ? 1 : -1; + case jbvObject: + if (a->object.npairs == b->object.npairs) + { + int i, + r; + + for (i = 0; i < a->object.npairs; i++) + { + if ((r = lexicalCompareJsonbStringValue(&a->object.pairs[i].key, + &b->object.pairs[i].key)) != 0) + return r; + if ((r = compareJsonbValue(&a->object.pairs[i].value, + &b->object.pairs[i].value)) != 0) + return r; + } + + return 0; + } + + return (a->object.npairs > b->object.npairs) ? 1 : -1; + case jbvBinary: + return compareJsonbBinaryValue(a->binary.data, b->binary.data); + default: + elog(ERROR, "invalid jsonb scalar type"); + } + } + + return (a->type > b->type) ? 1 : -1; + } + + /* + * Gives consistent ordering of Jsonb values + */ + int + compareJsonbBinaryValue(char *a, char *b) + { + JsonbIterator *it1, + *it2; + int res = 0; + + it1 = JsonbIteratorInit(a); + it2 = JsonbIteratorInit(b); + + while (res == 0) + { + JsonbValue v1, + v2; + int r1, + r2; + + r1 = JsonbIteratorGet(&it1, &v1, false); + r2 = JsonbIteratorGet(&it2, &v2, false); + + if (r1 == r2) + { + if (r1 == 0) + break; /* equal */ + + if (v1.type == v2.type) + { + switch (v1.type) + { + case jbvString: + res = lexicalCompareJsonbStringValue(&v1, &v2); + break; + case jbvBool: + if (v1.boolean == v2.boolean) + res = 0; + else + res = (v1.boolean > v2.boolean) ? 1 : -1; + break; + case jbvNumeric: + res = DatumGetInt32(DirectFunctionCall2(numeric_cmp, + PointerGetDatum(v1.numeric), + PointerGetDatum(v2.numeric))); + break; + case jbvArray: + if (v1.array.nelems != v2.array.nelems) + res = (v1.array.nelems > v2.array.nelems) ? 1 : -1; + break; + case jbvObject: + if (v1.object.npairs != v2.object.npairs) + res = (v1.object.npairs > v2.object.npairs) ? 1 : -1; + break; + default: + break; + } + } + else + { + res = (v1.type > v2.type) ? 1 : -1; /* dummy order */ + } + } + else + { + res = (r1 > r2) ? 1 : -1; /* dummy order */ + } + } + + return res; + } + + /* + * Find string key in object or element by value in array + */ + JsonbValue * + findUncompressedJsonbValueByValue(char *buffer, uint32 flags, + uint32 *lowbound, JsonbValue * key) + { + uint32 header = *(uint32 *) buffer; + static JsonbValue r; + + Assert((header & (JB_FLAG_ARRAY | JB_FLAG_OBJECT)) != + (JB_FLAG_ARRAY | JB_FLAG_OBJECT)); + + if (flags & JB_FLAG_ARRAY & header) + { + JEntry *array = (JEntry *) (buffer + sizeof(header)); + char *data = (char *) (array + (header & JB_COUNT_MASK)); + int i; + + for (i = (lowbound) ? *lowbound : 0; i < (header & JB_COUNT_MASK); i++) + { + JEntry *e = array + i; + + if (JBE_ISNULL(*e) && key->type == jbvNull) + { + r.type = jbvNull; + if (lowbound) + *lowbound = i; + r.size = sizeof(JEntry); + + return &r; + } + else if (JBE_ISSTRING(*e) && key->type == jbvString) + { + if (key->string.len == JBE_LEN(*e) && + memcmp(key->string.val, data + JBE_OFF(*e), + key->string.len) == 0) + { + r.type = jbvString; + r.string.val = data + JBE_OFF(*e); + r.string.len = key->string.len; + r.size = sizeof(JEntry) + r.string.len; + if (lowbound) + *lowbound = i; + + return &r; + } + } + else if (JBE_ISBOOL(*e) && key->type == jbvBool) + { + if ((JBE_ISBOOL_TRUE(*e) && key->boolean == true) || + (JBE_ISBOOL_FALSE(*e) && key->boolean == false)) + { + r = *key; + r.size = sizeof(JEntry); + if (lowbound) + *lowbound = i; + + return &r; + } + } + else if (JBE_ISNUMERIC(*e) && key->type == jbvNumeric) + { + if (DatumGetBool(DirectFunctionCall2(numeric_eq, + PointerGetDatum(data + INTALIGN(JBE_OFF(*e))), + PointerGetDatum(key->numeric))) == true) + { + r.type = jbvNumeric; + r.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e))); + + if (lowbound) + *lowbound = i; + + return &r; + } + } + } + } + else if (flags & JB_FLAG_OBJECT & header) + { + JEntry *array = (JEntry *) (buffer + sizeof(header)); + char *data = (char *) (array + (header & JB_COUNT_MASK) * 2); + uint32 stopLow = lowbound ? *lowbound : 0, + stopHigh = (header & JB_COUNT_MASK), + stopMiddle; + + if (key->type != jbvString) + return NULL; + + while (stopLow < stopHigh) + { + int difference; + JEntry *e; + + stopMiddle = stopLow + (stopHigh - stopLow) / 2; + + e = array + stopMiddle * 2; + + if (key->string.len == JBE_LEN(*e)) + difference = memcmp(data + JBE_OFF(*e), key->string.val, + key->string.len); + else + difference = (JBE_LEN(*e) > key->string.len) ? 1 : -1; + + if (difference == 0) + { + JEntry *v = e + 1; + + if (lowbound) + *lowbound = stopMiddle + 1; + + if (JBE_ISSTRING(*v)) + { + r.type = jbvString; + r.string.val = data + JBE_OFF(*v); + r.string.len = JBE_LEN(*v); + r.size = sizeof(JEntry) + r.string.len; + } + else if (JBE_ISBOOL(*v)) + { + r.type = jbvBool; + r.boolean = (JBE_ISBOOL_TRUE(*v)) != 0; + r.size = sizeof(JEntry); + } + else if (JBE_ISNUMERIC(*v)) + { + r.type = jbvNumeric; + r.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*v))); + + r.size = 2 * sizeof(JEntry) + VARSIZE_ANY(r.numeric); + } + else if (JBE_ISNULL(*v)) + { + r.type = jbvNull; + r.size = sizeof(JEntry); + } + else + { + r.type = jbvBinary; + r.binary.data = data + INTALIGN(JBE_OFF(*v)); + r.binary.len = JBE_LEN(*v) - + (INTALIGN(JBE_OFF(*v)) - JBE_OFF(*v)); + r.size = 2 * sizeof(JEntry) + r.binary.len; + } + + return &r; + } + else if (difference < 0) + { + stopLow = stopMiddle + 1; + } + else + { + stopHigh = stopMiddle; + } + } + + if (lowbound) + *lowbound = stopLow; + } + + return NULL; + } + + /* + * findUncompressedJsonbValueByValue() wrapper that sets up JsonbValue key. + */ + JsonbValue * + findUncompressedJsonbValue(char *buffer, uint32 flags, uint32 *lowbound, + char *key, uint32 keylen) + { + JsonbValue v; + + if (key == NULL) + { + v.type = jbvNull; + } + else + { + v.type = jbvString; + v.string.val = key; + v.string.len = keylen; + } + + return findUncompressedJsonbValueByValue(buffer, flags, lowbound, &v); + } + + /* + * Get i-th value of array or object. If i < 0, then it counts from the end of + * array/object. Note: returns pointer to statically allocated JsonbValue. + */ + JsonbValue * + getJsonbValue(char *buffer, uint32 flags, int32 i) + { + uint32 header = *(uint32 *) buffer; + static JsonbValue r; + JEntry *array, + *e; + char *data; + + Assert((header & (JB_FLAG_ARRAY | JB_FLAG_OBJECT)) != + (JB_FLAG_ARRAY | JB_FLAG_OBJECT)); + + if (i >= 0) + { + if (i >= (header & JB_COUNT_MASK)) + return NULL; + } + else + { + if (-i > (header & JB_COUNT_MASK)) + return NULL; + + i = (header & JB_COUNT_MASK) + i; + } + + array = (JEntry *) (buffer + sizeof(header)); + + if (flags & JB_FLAG_ARRAY & header) + { + e = array + i; + data = (char *) (array + (header & JB_COUNT_MASK)); + } + else if (flags & JB_FLAG_OBJECT & header) + { + e = array + i * 2 + 1; + data = (char *) (array + (header & JB_COUNT_MASK) * 2); + } + else + { + return NULL; + } + + if (JBE_ISSTRING(*e)) + { + r.type = jbvString; + r.string.val = data + JBE_OFF(*e); + r.string.len = JBE_LEN(*e); + r.size = sizeof(JEntry) + r.string.len; + } + else if (JBE_ISBOOL(*e)) + { + r.type = jbvBool; + r.boolean = (JBE_ISBOOL_TRUE(*e)) != 0; + r.size = sizeof(JEntry); + } + else if (JBE_ISNUMERIC(*e)) + { + r.type = jbvNumeric; + r.numeric = (Numeric) (data + INTALIGN(JBE_OFF(*e))); + + r.size = 2 * sizeof(JEntry) + VARSIZE_ANY(r.numeric); + } + else if (JBE_ISNULL(*e)) + { + r.type = jbvNull; + r.size = sizeof(JEntry); + } + else + { + r.type = jbvBinary; + r.binary.data = data + INTALIGN(JBE_OFF(*e)); + r.binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e)); + r.size = r.binary.len + 2 * sizeof(JEntry); + } + + return &r; + } + + /* + * Push JsonbValue into ToJsonbState. + * + * With r = WJB_END_OBJECT and v = NULL, this function sorts and unique-ifys + * the passed object's key values. Otherwise, they are assumed to already be + * sorted and unique. + * + * Initial state of ToJsonbState is NULL. + */ + JsonbValue * + pushJsonbValue(ToJsonbState ** state, int r, JsonbValue * v) + { + JsonbValue *h = NULL; + + switch (r) + { + case WJB_BEGIN_ARRAY: + *state = pushState(state); + h = &(*state)->v; + (*state)->v.type = jbvArray; + (*state)->v.size = 3 * sizeof(JEntry); + (*state)->v.array.nelems = 0; + (*state)->v.array.scalar = (v && v->array.scalar) != 0; + (*state)->size = (v && v->type == jbvArray && v->array.nelems > 0) + ? v->array.nelems : 4; + (*state)->v.array.elems = palloc(sizeof(*(*state)->v.array.elems) * + (*state)->size); + break; + case WJB_BEGIN_OBJECT: + *state = pushState(state); + h = &(*state)->v; + (*state)->v.type = jbvObject; + (*state)->v.size = 3 * sizeof(JEntry); + (*state)->v.object.npairs = 0; + (*state)->size = (v && v->type == jbvObject && v->object.npairs > 0) ? + v->object.npairs : 4; + (*state)->v.object.pairs = palloc(sizeof(*(*state)->v.object.pairs) * + (*state)->size); + break; + case WJB_ELEM: + Assert(v->type == jbvNull || v->type == jbvString || + v->type == jbvBool || v->type == jbvNumeric || + v->type == jbvBinary); + appendArray(*state, v); + break; + case WJB_KEY: + Assert(v->type == jbvString); + appendKey(*state, v); + break; + case WJB_VALUE: + Assert(v->type == jbvNull || v->type == jbvString || + v->type == jbvBool || v->type == jbvNumeric || + v->type == jbvBinary); + appendValue(*state, v); + break; + case WJB_END_OBJECT: + h = &(*state)->v; + /* + * When v != NULL and control reaches here, keys should already be + * sorted + */ + if (v == NULL) + uniqueifyJsonbValue(h); + + /* + * No break statement here - fall through and perform those steps + * required for the WJB_END_ARRAY case too. The end of a jsonb + * "object" associative structure may require us to first + * unique-ify its values, but values must then be appended to state + * in the same fashion as for an array. + */ + case WJB_END_ARRAY: + h = &(*state)->v; + + /* + * Pop stack and push current array/"object" as value in parent + * array/"object" + */ + *state = (*state)->next; + if (*state) + { + switch ((*state)->v.type) + { + case jbvArray: + appendArray(*state, h); + break; + case jbvObject: + appendValue(*state, h); + break; + default: + elog(ERROR, "invalid jsonb container type"); + } + } + break; + default: + elog(ERROR, "invalid jsonb container type"); + } + + return h; + } + + int + JsonbIteratorGet(JsonbIterator ** it, JsonbValue * v, bool skipNested) + { + int res; + + if (*it == NULL) + return 0; + + check_stack_depth(); + + /* + * Encode all possible states in the "type" integer. This is possible + * because enum members of JsonbIterator->state use different bit values + * than JB_FLAG_ARRAY/JB_FLAG_OBJECT. See definition of JsonbIterator + */ + switch ((*it)->type | (*it)->state) + { + case JB_FLAG_ARRAY | jbi_start: + (*it)->state = jbi_elem; + (*it)->i = 0; + v->type = jbvArray; + v->array.nelems = (*it)->nelems; + res = WJB_BEGIN_ARRAY; + v->array.scalar = (*it)->isScalar; + break; + case JB_FLAG_ARRAY | jbi_elem: + if ((*it)->i >= (*it)->nelems) + { + *it = up(*it); + res = WJB_END_ARRAY; + } + else if (formAnswer(it, v, &(*it)->array[(*it)->i++], skipNested)) + { + res = JsonbIteratorGet(it, v, skipNested); + } + else + { + res = WJB_ELEM; + } + break; + case JB_FLAG_OBJECT | jbi_start: + (*it)->state = jbi_key; + (*it)->i = 0; + v->type = jbvObject; + v->object.npairs = (*it)->nelems; + res = WJB_BEGIN_OBJECT; + break; + case JB_FLAG_OBJECT | jbi_key: + if ((*it)->i >= (*it)->nelems) + { + *it = up(*it); + res = WJB_END_OBJECT; + } + else + { + formAnswer(it, v, &(*it)->array[(*it)->i * 2], false); + (*it)->state = jbi_value; + res = WJB_KEY; + } + break; + case JB_FLAG_OBJECT | jbi_value: + (*it)->state = jbi_key; + if (formAnswer(it, v, &(*it)->array[((*it)->i++) * 2 + 1], skipNested)) + res = JsonbIteratorGet(it, v, skipNested); + else + res = WJB_VALUE; + break; + default: + elog(ERROR, "unexpected jsonb iterator's state"); + } + + return res; + } + + JsonbIterator * + JsonbIteratorInit(char *buffer) + { + JsonbIterator *it = palloc(sizeof(*it)); + + parseBuffer(it, buffer); + it->next = NULL; + + return it; + } + + /**************************************************************************** + * Walk the tree representation of jsonb * + ****************************************************************************/ + static void + walkUncompressedJsonbDo(JsonbValue * v, walk_jsonb_cb cb, void *cb_arg, + uint32 level) + { + int i; + + switch (v->type) + { + case jbvArray: + cb(cb_arg, v, WJB_BEGIN_ARRAY, level); + for (i = 0; i < v->array.nelems; i++) + { + if (v->array.elems[i].type == jbvNull || + v->array.elems[i].type == jbvString || + v->array.elems[i].type == jbvBool || + v->array.elems[i].type == jbvNumeric || + v->array.elems[i].type == jbvBinary) + cb(cb_arg, v->array.elems + i, WJB_ELEM, level); + else + walkUncompressedJsonbDo(v->array.elems + i, cb, cb_arg, + level + 1); + } + cb(cb_arg, v, WJB_END_ARRAY, level); + break; + case jbvObject: + cb(cb_arg, v, WJB_BEGIN_OBJECT, level); + + for (i = 0; i < v->object.npairs; i++) + { + cb(cb_arg, &v->object.pairs[i].key, WJB_KEY, level); + + if (v->object.pairs[i].value.type == jbvNull || + v->object.pairs[i].value.type == jbvString || + v->object.pairs[i].value.type == jbvBool || + v->object.pairs[i].value.type == jbvNumeric || + v->object.pairs[i].value.type == jbvBinary) + cb(cb_arg, &v->object.pairs[i].value, WJB_VALUE, level); + else + walkUncompressedJsonbDo(&v->object.pairs[i].value, cb, cb_arg, + level + 1); + } + + cb(cb_arg, v, WJB_END_OBJECT, level); + break; + default: + elog(ERROR, "unknown type of jsonb container"); + } + } + + static void + walkUncompressedJsonb(JsonbValue * v, walk_jsonb_cb cb, void *cb_arg) + { + if (v) + walkUncompressedJsonbDo(v, cb, cb_arg, 0); + } + + /**************************************************************************** + * Iteration over binary jsonb * + ****************************************************************************/ + static void + parseBuffer(JsonbIterator * it, char *buffer) + { + uint32 header = *(uint32 *) buffer; + + it->type = header & (JB_FLAG_ARRAY | JB_FLAG_OBJECT); + it->nelems = header & JB_COUNT_MASK; + it->buffer = buffer; + + + buffer += sizeof(uint32); + it->array = (JEntry *) buffer; + + it->state = jbi_start; + + switch (it->type) + { + case JB_FLAG_ARRAY: + it->data = buffer + it->nelems * sizeof(JEntry); + it->isScalar = (header & JB_FLAG_SCALAR) != 0; + Assert(it->isScalar == false || it->nelems == 1); + break; + case JB_FLAG_OBJECT: + it->data = buffer + it->nelems * sizeof(JEntry) * 2; + break; + default: + elog(ERROR, "unknown type of jsonb container"); + } + } + + static bool + formAnswer(JsonbIterator ** it, JsonbValue * v, JEntry * e, bool skipNested) + { + if (JBE_ISSTRING(*e)) + { + v->type = jbvString; + v->string.val = (*it)->data + JBE_OFF(*e); + v->string.len = JBE_LEN(*e); + v->size = sizeof(JEntry) + v->string.len; + + return false; + } + else if (JBE_ISBOOL(*e)) + { + v->type = jbvBool; + v->boolean = JBE_ISBOOL_TRUE(*e) != 0; + v->size = sizeof(JEntry); + + return false; + } + else if (JBE_ISNUMERIC(*e)) + { + v->type = jbvNumeric; + v->numeric = (Numeric) ((*it)->data + INTALIGN(JBE_OFF(*e))); + + v->size = 2 * sizeof(JEntry) + VARSIZE_ANY(v->numeric); + + return false; + } + else if (JBE_ISNULL(*e)) + { + v->type = jbvNull; + v->size = sizeof(JEntry); + + return false; + } + else if (skipNested) + { + v->type = jbvBinary; + v->binary.data = (*it)->data + INTALIGN(JBE_OFF(*e)); + v->binary.len = JBE_LEN(*e) - (INTALIGN(JBE_OFF(*e)) - JBE_OFF(*e)); + v->size = v->binary.len + 2 * sizeof(JEntry); + + return false; + } + else + { + JsonbIterator *nit = palloc(sizeof(*nit)); + + parseBuffer(nit, (*it)->data + INTALIGN(JBE_OFF(*e))); + nit->next = *it; + *it = nit; + + return true; + } + } + + static JsonbIterator * + up(JsonbIterator * it) + { + JsonbIterator *v = it->next; + + pfree(it); + + return v; + } + + /**************************************************************************** + * Transformation from tree to binary representation of jsonb * + ****************************************************************************/ + + #define curLevelState state->lptr + #define prevLevelState state->pptr + + static void + putJEntryString(CompressState * state, JsonbValue * value, uint32 level, uint32 i) + { + curLevelState = state->levelstate + level; + + if (i == 0) + curLevelState->array[0].entry = JENTRY_ISFIRST; + else + curLevelState->array[i].entry = 0; + + switch (value->type) + { + case jbvNull: + curLevelState->array[i].entry |= JENTRY_ISNULL; + + if (i > 0) + curLevelState->array[i].entry |= + curLevelState->array[i - 1].entry & JENTRY_POSMASK; + break; + case jbvString: + memcpy(state->ptr, value->string.val, value->string.len); + state->ptr += value->string.len; + + if (i == 0) + curLevelState->array[i].entry |= value->string.len; + else + curLevelState->array[i].entry |= + (curLevelState->array[i - 1].entry & JENTRY_POSMASK) + + value->string.len; + break; + case jbvBool: + curLevelState->array[i].entry |= (value->boolean) ? + JENTRY_ISTRUE : JENTRY_ISFALSE; + + if (i > 0) + curLevelState->array[i].entry |= + curLevelState->array[i - 1].entry & JENTRY_POSMASK; + break; + case jbvNumeric: + { + int addlen = INTALIGN(state->ptr - state->begin) - + (state->ptr - state->begin); + int numlen = VARSIZE_ANY(value->numeric); + + switch (addlen) + { + case 3: + *state->ptr = '\0'; + state->ptr++; + case 2: + *state->ptr = '\0'; + state->ptr++; + case 1: + *state->ptr = '\0'; + state->ptr++; + case 0: + default: + break; + } + + memcpy(state->ptr, value->numeric, numlen); + state->ptr += numlen; + + curLevelState->array[i].entry |= JENTRY_ISNUMERIC; + if (i == 0) + curLevelState->array[i].entry |= addlen + numlen; + else + curLevelState->array[i].entry |= + (curLevelState->array[i - 1].entry & JENTRY_POSMASK) + + addlen + numlen; + break; + } + case jbvBinary: + { + int addlen = INTALIGN(state->ptr - state->begin) - + (state->ptr - state->begin); + + switch (addlen) + { + case 3: + *state->ptr = '\0'; + state->ptr++; + case 2: + *state->ptr = '\0'; + state->ptr++; + case 1: + *state->ptr = '\0'; + state->ptr++; + case 0: + default: + break; + } + + memcpy(state->ptr, value->binary.data, value->binary.len); + state->ptr += value->binary.len; + + curLevelState->array[i].entry |= JENTRY_ISNEST; + + if (i == 0) + curLevelState->array[i].entry |= addlen + value->binary.len; + else + curLevelState->array[i].entry |= + (curLevelState->array[i - 1].entry & JENTRY_POSMASK) + + addlen + value->binary.len; + } + break; + default: + elog(ERROR, "invalid jsonb scalar type"); + } + } + + static void + compressCallback(void *arg, JsonbValue * value, uint32 flags, uint32 level) + { + CompressState *state = arg; + + if (level == state->maxlevel) + { + state->maxlevel *= 2; + state->levelstate = repalloc(state->levelstate, + sizeof(*state->levelstate) * state->maxlevel); + } + + curLevelState = state->levelstate + level; + + if (flags & (WJB_BEGIN_ARRAY | WJB_BEGIN_OBJECT)) + { + Assert(((flags & WJB_BEGIN_ARRAY) && value->type == jbvArray) || + ((flags & WJB_BEGIN_OBJECT) && value->type == jbvObject)); + + curLevelState->begin = state->ptr; + + switch (INTALIGN(state->ptr - state->begin) - + (state->ptr - state->begin)) + { + case 3: + *state->ptr = '\0'; + state->ptr++; + case 2: + *state->ptr = '\0'; + state->ptr++; + case 1: + *state->ptr = '\0'; + state->ptr++; + case 0: + default: + break; + } + + curLevelState->header = (uint32 *) state->ptr; + state->ptr += sizeof(*curLevelState->header); + + curLevelState->array = (JEntry *) state->ptr; + curLevelState->i = 0; + + if (value->type == jbvArray) + { + *curLevelState->header = value->array.nelems | JB_FLAG_ARRAY; + state->ptr += sizeof(JEntry) * value->array.nelems; + + if (value->array.scalar) + { + Assert(value->array.nelems == 1); + Assert(level == 0); + *curLevelState->header |= JB_FLAG_SCALAR; + } + } + else + { + *curLevelState->header = value->object.npairs | JB_FLAG_OBJECT; + state->ptr += sizeof(JEntry) * value->object.npairs * 2; + } + } + else if (flags & WJB_ELEM) + { + putJEntryString(state, value, level, curLevelState->i); + curLevelState->i++; + } + else if (flags & WJB_KEY) + { + Assert(value->type == jbvString); + + putJEntryString(state, value, level, curLevelState->i * 2); + } + else if (flags & WJB_VALUE) + { + putJEntryString(state, value, level, curLevelState->i * 2 + 1); + curLevelState->i++; + } + else if (flags & (WJB_END_ARRAY | WJB_END_OBJECT)) + { + uint32 len, + i; + + Assert(((flags & WJB_END_ARRAY) && value->type == jbvArray) || + ((flags & WJB_END_OBJECT) && value->type == jbvObject)); + if (level == 0) + return; + + len = state->ptr - (char *) curLevelState->begin; + + prevLevelState = curLevelState - 1; + + if (*prevLevelState->header & JB_FLAG_ARRAY) + { + i = prevLevelState->i; + + prevLevelState->array[i].entry = JENTRY_ISNEST; + + if (i == 0) + prevLevelState->array[0].entry |= JENTRY_ISFIRST | len; + else + prevLevelState->array[i].entry |= + (prevLevelState->array[i - 1].entry & JENTRY_POSMASK) + len; + } + else if (*prevLevelState->header & JB_FLAG_OBJECT) + { + i = 2 * prevLevelState->i + 1; /* VALUE, not a KEY */ + + prevLevelState->array[i].entry = JENTRY_ISNEST; + + prevLevelState->array[i].entry |= + (prevLevelState->array[i - 1].entry & JENTRY_POSMASK) + len; + } + else + { + elog(ERROR, "invalid jsonb container type"); + } + + Assert(state->ptr - curLevelState->begin <= value->size); + prevLevelState->i++; + } + else + { + elog(ERROR, "unknown flag in tree walk"); + } + } + + /* + * puts JsonbValue tree into preallocated buffer + */ + static uint32 + compressJsonb(JsonbValue * v, char *buffer) + { + uint32 l = 0; + CompressState state; + + state.begin = state.ptr = buffer; + state.maxlevel = 8; + state.levelstate = palloc(sizeof(*state.levelstate) * state.maxlevel); + + walkUncompressedJsonb(v, compressCallback, &state); + + l = state.ptr - buffer; + Assert(l <= v->size); + + return l; + } + + /**************************************************************************** + * Iteration-like forming jsonb * + ****************************************************************************/ + static ToJsonbState * + pushState(ToJsonbState ** state) + { + ToJsonbState *ns = palloc(sizeof(*ns)); + + ns->next = *state; + return ns; + } + + static void + appendArray(ToJsonbState * state, JsonbValue * v) + { + JsonbValue *a = &state->v; + + Assert(a->type == jbvArray); + + if (a->array.nelems >= state->size) + { + state->size *= 2; + a->array.elems = repalloc(a->array.elems, + sizeof(*a->array.elems) * state->size); + } + + a->array.elems[a->array.nelems++] = *v; + + a->size += v->size; + } + + static void + appendKey(ToJsonbState * state, JsonbValue * v) + { + JsonbValue *h = &state->v; + + Assert(h->type == jbvObject); + + if (h->object.npairs >= state->size) + { + state->size *= 2; + h->object.pairs = repalloc(h->object.pairs, + sizeof(*h->object.pairs) * state->size); + } + + h->object.pairs[h->object.npairs].key = *v; + h->object.pairs[h->object.npairs].order = h->object.npairs; + + h->size += v->size; + } + + static void + appendValue(ToJsonbState * state, JsonbValue * v) + { + JsonbValue *h = &state->v; + + Assert(h->type == jbvObject); + + h->object.pairs[h->object.npairs++].value = *v; + + h->size += v->size; + } + + /* + * qsort_arg() comparator to compare JsonbPair values. + * + * Function implemented in terms of compareJsonbStringValue(), and thus the + * same "arg setting" hack will be applied here in respect of the pair's key + * values. + * + * Pairs with equals keys are ordered such that the order field is respected. + */ + static int + compareJsonbPair(const void *a, const void *b, void *arg) + { + const JsonbPair *pa = a; + const JsonbPair *pb = b; + int res; + + res = compareJsonbStringValue(&pa->key, &pb->key, arg); + + /* + * 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 (associative "object" data + * structure) + */ + static void + uniqueifyJsonbValue(JsonbValue * v) + { + bool hasNonUniq = false; + + Assert(v->type == jbvObject); + + if (v->object.npairs > 1) + qsort_arg(v->object.pairs, v->object.npairs, sizeof(*v->object.pairs), + compareJsonbPair, &hasNonUniq); + + if (hasNonUniq) + { + JsonbPair *ptr = v->object.pairs + 1, + *res = v->object.pairs; + + while (ptr - v->object.pairs < v->object.npairs) + { + if (ptr->key.string.len == res->key.string.len && + memcmp(ptr->key.string.val, res->key.string.val, + ptr->key.string.len) == 0) + { + v->size -= ptr->key.size + ptr->value.size; + } + else + { + res++; + if (ptr != res) + memcpy(res, ptr, sizeof(*res)); + } + ptr++; + } + + v->object.npairs = res + 1 - v->object.pairs; + } + } *** a/src/backend/utils/adt/jsonfuncs.c --- b/src/backend/utils/adt/jsonfuncs.c *************** *** 27,32 **** --- 27,33 ---- #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" *************** static void get_array_element_end(void * *** 47,64 **** 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 text *get_worker(text *json, char *field, int elem_index, char **tpath, int *ipath, int npath, bool normalize_results); /* 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); /* semantic action functions for json_each */ static void each_object_field_start(void *state, char *fname, bool isnull); --- 48,67 ---- static void get_scalar(void *state, char *token, JsonTokenType tokentype); /* common worker function for json getter functions */ ! 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 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); *************** static void each_object_field_end(void * *** 66,73 **** 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); /* semantic action functions for json_array_elements */ static void elements_object_start(void *state); --- 69,77 ---- static void each_array_start(void *state); static void each_scalar(void *state, char *token, JsonTokenType tokentype); ! /* 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); *************** static void elements_scalar(void *state, *** 79,85 **** 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, bool have_record_arg); /* semantic action functions for get_json_object_as_hash */ --- 83,89 ---- 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(FunctionCallInfo fcinfo, bool have_record_arg); /* semantic action functions for get_json_object_as_hash */ *************** static void populate_recordset_array_sta *** 98,104 **** 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, bool have_record_arg); /* search type classification for json_get* functions */ --- 102,108 ---- 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(FunctionCallInfo fcinfo, bool have_record_arg); /* search type classification for json_get* functions */ *************** typedef struct PopulateRecordsetState *** 225,242 **** MemoryContext fn_mcxt; /* used to stash IO funcs */ } PopulateRecordsetState; /* ! * 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 * 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 --- 229,326 ---- 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 * * 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, 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 = 0; + + 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 = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + 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 *************** okeys_scalar(void *state, char *token, J *** 350,358 **** } /* ! * json getter functions * these implement the -> ->> #> and #>> operators ! * and the json_extract_path*(json, text, ...) functions */ --- 434,442 ---- } /* ! * json and jsonb getter functions * these implement the -> ->> #> and #>> operators ! * and the json{b?}_extract_path*(json, text, ...) functions */ *************** json_object_field(PG_FUNCTION_ARGS) *** 373,378 **** --- 457,507 ---- } 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 = 0; + bool skipNested = false; + + if (JB_ROOT_IS_SCALAR(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call jsonb_object_field on a scalar"))); + else if (JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call jsonb_object_field on an array"))); + + Assert(JB_ROOT_IS_OBJECT(jb)); + + it = JsonbIteratorInit(VARDATA_ANY(jb)); + + while ((r = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + 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. + */ + r = JsonbIteratorGet(&it, &v, skipNested); + PG_RETURN_JSONB(JsonbValueToJsonb(&v)); + } + } + } + + PG_RETURN_NULL(); + } + + Datum json_object_field_text(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_P(0); *************** json_object_field_text(PG_FUNCTION_ARGS) *** 389,394 **** --- 518,591 ---- } 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 = 0; + bool skipNested = false; + + if (JB_ROOT_IS_SCALAR(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call jsonb_object_field_text on a scalar"))); + else if (JB_ROOT_IS_ARRAY(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call jsonb_object_field_text on an array"))); + + Assert(JB_ROOT_IS_OBJECT(jb)); + + it = JsonbIteratorInit(VARDATA_ANY(jb)); + + while ((r = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + 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 = JsonbIteratorGet(&it, &v, skipNested); + + /* + * if it's a scalar string it needs to be de-escaped, + * otherwise just return the text + */ + if (v.type == jbvString) + { + result = cstring_to_text_with_len(v.string.val, v.string.len); + } + else if (v.type == jbvNull) + { + PG_RETURN_NULL(); + } + else + { + StringInfo jtext = makeStringInfo(); + Jsonb *tjb = JsonbValueToJsonb(&v); + + (void) JsonbToCString(jtext, VARDATA(tjb), -1); + result = cstring_to_text_with_len(jtext->data, jtext->len); + } + PG_RETURN_TEXT_P(result); + } + } + } + + PG_RETURN_NULL(); + } + + Datum json_array_element(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_P(0); *************** json_array_element(PG_FUNCTION_ARGS) *** 404,409 **** --- 601,644 ---- } 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 = 0; + 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 on a scalar"))); + else if (JB_ROOT_IS_OBJECT(jb)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot call jsonb_array_element on an object"))); + + Assert(JB_ROOT_IS_ARRAY(jb)); + + it = JsonbIteratorInit(VARDATA_ANY(jb)); + + while ((r = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + skipNested = true; + + if (r == WJB_ELEM) + { + if (element_number++ == element) + PG_RETURN_JSONB(JsonbValueToJsonb(&v)); + } + } + + PG_RETURN_NULL(); + } + + Datum json_array_element_text(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_P(0); *************** json_array_element_text(PG_FUNCTION_ARGS *** 419,424 **** --- 654,722 ---- } 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 = 0; + 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 = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + skipNested = true; + + if (r == WJB_ELEM) + { + if (element_number++ == element) + { + /* + * if it's a scalar string it needs to be de-escaped, + * otherwise just return the text + */ + text *result; + + if (v.type == jbvString) + { + result = cstring_to_text_with_len(v.string.val, v.string.len); + } + else if (v.type == jbvNull) + { + PG_RETURN_NULL(); + } + else + { + StringInfo jtext = makeStringInfo(); + Jsonb *tjb = JsonbValueToJsonb(&v); + + (void) JsonbToCString(jtext, VARDATA(tjb), -1); + result = cstring_to_text_with_len(jtext->data, jtext->len); + } + PG_RETURN_TEXT_P(result); + } + } + } + + PG_RETURN_NULL(); + } + + Datum json_extract_path(PG_FUNCTION_ARGS) { return get_path_all(fcinfo, false); *************** json_extract_path_text(PG_FUNCTION_ARGS) *** 434,442 **** * common routine for extract_path functions */ static inline Datum ! get_path_all(PG_FUNCTION_ARGS, bool as_text) { ! text *json = PG_GETARG_TEXT_P(0); ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); text *result; Datum *pathtext; --- 732,741 ---- * common routine for extract_path functions */ static inline Datum ! get_path_all(FunctionCallInfo fcinfo, bool as_text) { ! Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); ! text *json; ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); text *result; Datum *pathtext; *************** get_path_all(PG_FUNCTION_ARGS, bool as_t *** 448,453 **** --- 747,765 ---- long ind; char *endptr; + Assert(val_type == JSONOID || val_type == JSONBOID); + if (val_type == JSONOID) + { + /* just get the text */ + json = PG_GETARG_TEXT_P(0); + } + else + { + Jsonb *jb = PG_GETARG_JSONB(0); + + json = cstring_to_text(JsonbToCString(NULL, (JB_ISEMPTY(jb)) ? NULL : VARDATA(jb), VARSIZE(jb))); + } + if (array_contains_nulls(path)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), *************** get_path_all(PG_FUNCTION_ARGS, bool as_t *** 486,494 **** result = get_worker(json, NULL, -1, tpath, ipath, npath, as_text); if (result != NULL) ! PG_RETURN_TEXT_P(result); else PG_RETURN_NULL(); } /* --- 798,814 ---- result = get_worker(json, NULL, -1, tpath, ipath, npath, as_text); if (result != NULL) ! { ! if (val_type == JSONOID || as_text) ! PG_RETURN_TEXT_P(result); ! else ! PG_RETURN_JSONB(DirectFunctionCall1(jsonb_in, CStringGetDatum(text_to_cstring(result)))); ! } else + { + /* null is NULL, regardless */ PG_RETURN_NULL(); + } } /* *************** get_object_field_end(void *state, char * *** 668,674 **** /* * 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). */ int len = _state->lex->prev_token_terminator - _state->result_start; --- 988,994 ---- /* * 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 whatever came after what we're interested in). */ int len = _state->lex->prev_token_terminator - _state->result_start; *************** get_scalar(void *state, char *token, Jso *** 822,839 **** } /* * SQL function json_array_length(json) -> int */ Datum json_array_length(PG_FUNCTION_ARGS) { ! text *json = PG_GETARG_TEXT_P(0); AlenState *state; ! JsonLexContext *lex = makeJsonLexContext(json, false); JsonSemAction *sem; state = palloc0(sizeof(AlenState)); sem = palloc0(sizeof(JsonSemAction)); --- 1142,1275 ---- } + 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; + JsonbValue tv; + + + 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; + + jbvp = (JsonbValue *) VARDATA(jb); + + for (i = 0; i < npath; i++) + { + if (have_object) + { + jbvp = findUncompressedJsonbValue((char *) jbvp, JB_FLAG_OBJECT, NULL, + 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 = getJsonbValue((char *) jbvp, JB_FLAG_ARRAY, 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(); + if (i == npath - 1) + break; + if (jbvp->type == jbvBinary) + { + JsonbIterator *it = JsonbIteratorInit(jbvp->binary.data); + int r; + + r = JsonbIteratorGet(&it, &tv, true); + jbvp = (JsonbValue *) 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, (JB_ISEMPTY(res)) ? 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; AlenState *state; ! JsonLexContext *lex; JsonSemAction *sem; + json = PG_GETARG_TEXT_P(0); + lex = makeJsonLexContext(json, false); state = palloc0(sizeof(AlenState)); sem = palloc0(sizeof(JsonSemAction)); *************** json_array_length(PG_FUNCTION_ARGS) *** 853,858 **** --- 1289,1311 ---- 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). *************** json_each(PG_FUNCTION_ARGS) *** 909,932 **** } Datum json_each_text(PG_FUNCTION_ARGS) { return each_worker(fcinfo, true); } static inline Datum ! each_worker(PG_FUNCTION_ARGS, bool as_text) { ! text *json = PG_GETARG_TEXT_P(0); ! JsonLexContext *lex = makeJsonLexContext(json, true); ! JsonSemAction *sem; ReturnSetInfo *rsi; ! MemoryContext old_cxt; TupleDesc tupdesc; ! EachState *state; ! state = palloc0(sizeof(EachState)); ! sem = palloc0(sizeof(JsonSemAction)); rsi = (ReturnSetInfo *) fcinfo->resultinfo; --- 1362,1404 ---- } Datum + jsonb_each(PG_FUNCTION_ARGS) + { + return each_worker_jsonb(fcinfo, false); + } + + Datum json_each_text(PG_FUNCTION_ARGS) { return each_worker(fcinfo, true); } + Datum + jsonb_each_text(PG_FUNCTION_ARGS) + { + return each_worker_jsonb(fcinfo, true); + } + static inline Datum ! each_worker_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 = 0; ! 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; *************** each_worker(PG_FUNCTION_ARGS, bool as_te *** 947,952 **** --- 1419,1556 ---- 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 = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + 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 = JsonbIteratorGet(&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.size); + 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); *************** each_scalar(void *state, char *token, Js *** 1087,1092 **** --- 1691,1836 ---- * * 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 = 0; + + 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 = JsonbIteratorGet(&it, &v, skipNested)) != 0) + { + 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.size); + 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) { *************** json_array_elements_text(PG_FUNCTION_ARG *** 1100,1106 **** } static inline Datum ! elements_worker(PG_FUNCTION_ARGS, bool as_text) { text *json = PG_GETARG_TEXT_P(0); --- 1844,1850 ---- } static inline Datum ! elements_worker(FunctionCallInfo fcinfo, bool as_text) { text *json = PG_GETARG_TEXT_P(0); *************** elements_scalar(void *state, char *token *** 1270,1278 **** * 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. */ Datum json_populate_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, true); --- 2014,2029 ---- * 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. For jsonb ! * we fetch the values direct from the object. */ Datum + jsonb_populate_record(PG_FUNCTION_ARGS) + { + return populate_record_worker(fcinfo, true); + } + + Datum json_populate_record(PG_FUNCTION_ARGS) { return populate_record_worker(fcinfo, true); *************** json_to_record(PG_FUNCTION_ARGS) *** 1285,1295 **** } static inline Datum ! populate_record_worker(PG_FUNCTION_ARGS, bool have_record_arg) { text *json; bool use_json_as_text; ! HTAB *json_hash; HeapTupleHeader rec = NULL; Oid tupType = InvalidOid; int32 tupTypmod = -1; --- 2036,2049 ---- } static inline Datum ! 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 = NULL; HeapTupleHeader rec = NULL; Oid tupType = InvalidOid; int32 tupTypmod = -1; *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1301,1319 **** int i; Datum *values; bool *nulls; ! char fname[NAMEDATALEN]; ! JsonHashEntry *hashentry; 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); if (!type_is_rowtype(argtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("first argument of json_populate_record must be a row type"))); if (PG_ARGISNULL(0)) { --- 2055,2074 ---- int i; Datum *values; bool *nulls; ! ! 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) { ! argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); if (!type_is_rowtype(argtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), ! errmsg("first argument of json%s_populate_record must be a row type", jtype == JSONBOID ? "b" : ""))); if (PG_ARGISNULL(0)) { *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1340,1358 **** tupTypmod = HeapTupleHeaderGetTypMod(rec); } ! json = PG_GETARG_TEXT_P(1); } else ! { ! /* json_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), --- 2095,2110 ---- tupTypmod = HeapTupleHeaderGetTypMod(rec); } ! tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); } else ! { /* json{b}_to_record case */ use_json_as_text = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1); if (PG_ARGISNULL(0)) PG_RETURN_NULL(); if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1362,1372 **** "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 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 --- 2114,2126 ---- "using a column definition list."))); } ! 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 *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1375,1382 **** if (hash_get_num_entries(json_hash) == 0 && rec) PG_RETURN_POINTER(rec); ! tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); } ncolumns = tupdesc->natts; --- 2129,2142 ---- if (hash_get_num_entries(json_hash) == 0 && rec) PG_RETURN_POINTER(rec); + } + else + { + jb = PG_GETARG_JSONB(have_record_arg ? 1 : 0); ! /* same logic as for json */ ! if (JB_ISEMPTY(jb) && rec) ! PG_RETURN_POINTER(rec); } ncolumns = tupdesc->natts; *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1439,1445 **** { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; ! char *value; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) --- 2199,2207 ---- { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; ! JsonbValue *v = NULL; ! char fname[NAMEDATALEN]; ! JsonHashEntry *hashentry = NULL; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1448,1456 **** continue; } ! memset(fname, 0, NAMEDATALEN); ! strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN); ! hashentry = hash_search(json_hash, fname, HASH_FIND, NULL); /* * we can't just skip here if the key wasn't found since we might have --- 2210,2231 ---- continue; } ! 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 ! { ! if (!JB_ISEMPTY(jb)) ! { ! char *key = NameStr(tupdesc->attrs[i]->attname); ! ! v = findUncompressedJsonbValue(VARDATA(jb), JB_FLAG_OBJECT, NULL, key, strlen(key)); ! } ! } /* * we can't just skip here if the key wasn't found since we might have *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1460,1466 **** * 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) continue; /* --- 2235,2242 ---- * 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 (((jtype == JSONOID && hashentry == NULL) || ! (jtype == JSONBOID && v == NULL)) && rec) continue; /* *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1475,1481 **** fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } ! if (hashentry == NULL || hashentry->isnull) { /* * need InputFunctionCall to happen even for nulls, so that domain --- 2251,2258 ---- fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } ! if ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) || ! (jtype == JSONBOID && (v == NULL || v->type == jbvNull))) { /* * need InputFunctionCall to happen even for nulls, so that domain *************** populate_record_worker(PG_FUNCTION_ARGS, *** 1488,1496 **** } else { ! value = hashentry->val; ! values[i] = InputFunctionCall(&column_info->proc, value, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = false; --- 2265,2297 ---- } else { ! char *s = NULL; ! 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; *************** hash_scalar(void *state, char *token, Js *** 1656,1661 **** --- 2457,2591 ---- * 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; + + /* Ignore dropped columns in datatype */ + if (tupdesc->attrs[i]->attisdropped) + { + nulls[i] = true; + continue; + } + + if (!JB_ISEMPTY(element)) + { + char *key = NameStr(tupdesc->attrs[i]->attname); + + v = findUncompressedJsonbValue(VARDATA(element), JB_FLAG_OBJECT, NULL, key, strlen(key)); + } + + /* + * We can't just skip here if the key wasn't found since we might have + * a domain to deal with. If we were passed in a non-null record + * datum, we assume that the existing values are valid (if they're + * not, then it's not our fault), but if we were passed in a null, + * then every field which we don't populate needs to be run through + * the input function just in case it's a domain type. + */ + if (v == NULL && rec) + continue; + + /* + * Prepare to convert the column value from text + */ + if (column_info->column_type != column_type) + { + getTypeInputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + state->fn_mcxt); + column_info->column_type = column_type; + } + if (v == NULL || v->type == jbvNull) + { + /* + * Need InputFunctionCall to happen even for nulls, so that domain + * checks are done + */ + values[i] = InputFunctionCall(&column_info->proc, NULL, + column_info->typioparam, + tupdesc->attrs[i]->atttypmod); + nulls[i] = true; + } + else + { + char *s = NULL; + + if (v->type == jbvString) + s = pnstrdup(v->string.val, v->string.len); + else if (v->type == jbvBool) + s = pnstrdup((v->boolean) ? "t" : "f", 1); + else if (v->type == jbvNumeric) + s = DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(v->numeric))); + else if (!state->use_json_as_text) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot populate with a nested object unless use_json_as_text is true"))); + else if (v->type == jbvBinary) + s = JsonbToCString(NULL, v->binary.data, v->binary.len); + else + elog(ERROR, "invalid jsonb type"); + + values[i] = InputFunctionCall(&column_info->proc, s, + column_info->typioparam, + tupdesc->attrs[i]->atttypmod); + nulls[i] = false; + } + } + + rettuple = heap_form_tuple(tupdesc, values, nulls); + + tuplestore_puttuple(state->tuple_store, rettuple); + } + + Datum json_populate_recordset(PG_FUNCTION_ARGS) { return populate_recordset_worker(fcinfo, true); *************** json_to_recordset(PG_FUNCTION_ARGS) *** 1671,1680 **** * common worker for json_populate_recordset() and json_to_recordset() */ static inline Datum ! populate_recordset_worker(PG_FUNCTION_ARGS, bool have_record_arg) { Oid argtype; ! text *json; bool use_json_as_text; ReturnSetInfo *rsi; MemoryContext old_cxt; --- 2601,2610 ---- * common worker for json_populate_recordset() and json_to_recordset() */ static inline Datum ! populate_recordset_worker(FunctionCallInfo fcinfo, bool have_record_arg) { Oid argtype; ! Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, have_record_arg ? 1 : 0); bool use_json_as_text; ReturnSetInfo *rsi; MemoryContext old_cxt; *************** populate_recordset_worker(PG_FUNCTION_AR *** 1684,1691 **** TupleDesc tupdesc; RecordIOData *my_extra; int ncolumns; - JsonLexContext *lex; - JsonSemAction *sem; PopulateRecordsetState *state; if (have_record_arg) --- 2614,2619 ---- *************** populate_recordset_worker(PG_FUNCTION_AR *** 1721,1727 **** /* * get the tupdesc from the result set info - it must be a record type ! * because we already checked that arg1 is a record type. */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) ereport(ERROR, --- 2649,2656 ---- /* * get the tupdesc from the result set info - it must be 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, *************** populate_recordset_worker(PG_FUNCTION_AR *** 1729,1757 **** 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 --- 2658,2669 ---- *************** populate_recordset_worker(PG_FUNCTION_AR *** 1759,1769 **** } else { ! if (PG_ARGISNULL(0)) PG_RETURN_NULL(); - json = PG_GETARG_TEXT_P(0); - rec = NULL; } --- 2671,2679 ---- } else { ! if (PG_ARGISNULL(1)) PG_RETURN_NULL(); rec = NULL; } *************** populate_recordset_worker(PG_FUNCTION_AR *** 1771,1778 **** 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. --- 2681,2686 ---- *************** populate_recordset_worker(PG_FUNCTION_AR *** 1801,1823 **** 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->lex = lex; 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); rsi->setResult = state->tuple_store; rsi->setDesc = state->ret_tdesc; --- 2709,2788 ---- my_extra->ncolumns = ncolumns; } ! state = palloc0(sizeof(PopulateRecordsetState)); ! /* 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; ! 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 = JsonbIteratorGet(&it, &v, skipNested)) != 0) ! { ! 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; *** a/src/backend/utils/adt/numeric.c --- b/src/backend/utils/adt/numeric.c *************** numeric_out_sci(Numeric num, int scale) *** 627,632 **** --- 627,671 ---- } /* + * numeric_normalize() - + * + * Output function for numeric data type without trailing zeroes. + */ + char * + numeric_normalize(Numeric num) + { + NumericVar x; + char *str; + Size last; + + /* + * Handle NaN + */ + if (NUMERIC_IS_NAN(num)) + return pstrdup("NaN"); + + init_var_from_num(num, &x); + + str = get_str_from_var(&x); + + last = strlen(str) - 1; + + for (;;) + { + if (str[last] != '0') + break; + + str[last] = '\0'; + last--; + } + + if (str[last] == '.') + str[last] = '\0'; + + return str; + } + + /* * numeric_recv - converts external binary format to numeric * * External format is a sequence of int16's: *** a/src/include/catalog/pg_amop.h --- b/src/include/catalog/pg_amop.h *************** DATA(insert ( 4017 25 25 14 s 667 4000 *** 778,783 **** --- 778,819 ---- 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 )); + + /* + * GiST jsonb ops + */ + DATA(insert ( 4035 3802 3802 7 s 3246 783 0 )); + DATA(insert ( 4035 3802 25 9 s 3247 783 0 )); + DATA(insert ( 4035 3802 1009 10 s 3248 783 0 )); + DATA(insert ( 4035 3802 1009 11 s 3249 783 0 )); + DATA(insert ( 4035 3802 3802 13 s 3250 783 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 */ DATA(insert ( 3474 3831 3831 1 s 3893 4000 0 )); *** a/src/include/catalog/pg_amproc.h --- b/src/include/catalog/pg_amproc.h *************** DATA(insert ( 3522 3500 3500 1 3514 )) *** 138,143 **** --- 138,144 ---- 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 */ *************** DATA(insert ( 2235 1033 1033 1 329 )); *** 175,180 **** --- 176,182 ---- 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 */ *************** DATA(insert ( 3919 3831 3831 4 3878 )) *** 228,233 **** --- 230,242 ---- DATA(insert ( 3919 3831 3831 5 3879 )); DATA(insert ( 3919 3831 3831 6 3880 )); DATA(insert ( 3919 3831 3831 7 3881 )); + DATA(insert ( 4035 3802 3802 1 4052 )); + DATA(insert ( 4035 3802 3802 2 4053 )); + DATA(insert ( 4035 3802 3802 3 4054 )); + DATA(insert ( 4035 3802 3802 4 4055 )); + DATA(insert ( 4035 3802 3802 5 4056 )); + DATA(insert ( 4035 3802 3802 6 4057 )); + DATA(insert ( 4035 3802 3802 7 4058 )); /* gin */ *************** DATA(insert ( 3659 3614 3614 2 3656 )) *** 356,362 **** DATA(insert ( 3659 3614 3614 3 3657 )); DATA(insert ( 3659 3614 3614 4 3658 )); DATA(insert ( 3659 3614 3614 5 2700 )); ! /* sp-gist */ DATA(insert ( 3474 3831 3831 1 3469 )); --- 365,378 ---- DATA(insert ( 3659 3614 3614 3 3657 )); DATA(insert ( 3659 3614 3614 4 3658 )); DATA(insert ( 3659 3614 3614 5 2700 )); ! DATA(insert ( 4036 3802 3802 1 360 )); ! DATA(insert ( 4036 3802 3802 2 3482 )); ! DATA(insert ( 4036 3802 3802 3 3483 )); ! DATA(insert ( 4036 3802 3802 4 3484 )); ! 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 )); /* sp-gist */ DATA(insert ( 3474 3831 3831 1 3469 )); *** a/src/include/catalog/pg_cast.h --- b/src/include/catalog/pg_cast.h *************** DATA(insert ( 1560 1560 1685 i f )); *** 359,362 **** --- 359,366 ---- 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 */ *** a/src/include/catalog/pg_opclass.h --- b/src/include/catalog/pg_opclass.h *************** DATA(insert ( 4000 range_ops PGNSP PGU *** 228,232 **** --- 228,237 ---- 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 ( 783 jsonb_ops PGNSP PGUID 4035 3802 t 3801 )); + 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 */ *** a/src/include/catalog/pg_operator.h --- b/src/include/catalog/pg_operator.h *************** DATA(insert OID = 3966 ( "#>" PGNSP *** 1769,1776 **** 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"); ! ! /* * function prototypes --- 1769,1809 ---- 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 0 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 0 jsonb_contained contsel contjoinsel )); ! DESCR("contained"); /* * function prototypes *** a/src/include/catalog/pg_opfamily.h --- b/src/include/catalog/pg_opfamily.h *************** DATA(insert OID = 3474 ( 4000 range_ops *** 147,152 **** --- 147,157 ---- 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 */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DATA(insert OID = 3198 ( json_build_arr *** 4148,4160 **** DESCR("build a json array from any inputs"); DATA(insert OID = 3199 ( json_build_array PGNSP PGUID 12 1 0 0 0 f f f f f f i 0 0 114 "" _null_ _null_ _null_ _null_ json_build_array_noargs _null_ _null_ _null_ )); DESCR("build an empty json array"); ! DATA(insert OID = 3200 ( json_build_object PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 114 "2276" "{2276}" "{v}" _null_ _null_ json_build_object _null_ _null_ _null_ )); DESCR("build a json object from pairwise key/value inputs"); ! DATA(insert OID = 3201 ( json_build_object PGNSP PGUID 12 1 0 0 0 f f f f f f i 0 0 114 "" _null_ _null_ _null_ _null_ json_build_object_noargs _null_ _null_ _null_ )); DESCR("build an empty json object"); ! DATA(insert OID = 3202 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 114 "1009" _null_ _null_ _null_ _null_ json_object _null_ _null_ _null_ )); DESCR("map text arrayof key value pais to json object"); ! DATA(insert OID = 3203 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "1009 1009" _null_ _null_ _null_ _null_ json_object_two_arg _null_ _null_ _null_ )); DESCR("map text arrayof key value pais to json object"); DATA(insert OID = 3176 ( to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 114 "2283" _null_ _null_ _null_ _null_ to_json _null_ _null_ _null_ )); DESCR("map input to json"); --- 4148,4160 ---- DESCR("build a json array from any inputs"); DATA(insert OID = 3199 ( json_build_array PGNSP PGUID 12 1 0 0 0 f f f f f f i 0 0 114 "" _null_ _null_ _null_ _null_ json_build_array_noargs _null_ _null_ _null_ )); DESCR("build an empty json array"); ! DATA(insert OID = 3200 ( json_build_object PGNSP PGUID 12 1 0 2276 0 f f f f f f i 1 0 114 "2276" "{2276}" "{v}" _null_ _null_ json_build_object _null_ _null_ _null_ )); DESCR("build a json object from pairwise key/value inputs"); ! DATA(insert OID = 3201 ( json_build_object PGNSP PGUID 12 1 0 0 0 f f f f f f i 0 0 114 "" _null_ _null_ _null_ _null_ json_build_object_noargs _null_ _null_ _null_ )); DESCR("build an empty json object"); ! DATA(insert OID = 3202 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 114 "1009" _null_ _null_ _null_ _null_ json_object _null_ _null_ _null_ )); DESCR("map text arrayof key value pais to json object"); ! DATA(insert OID = 3203 ( json_object PGNSP PGUID 12 1 0 0 0 f f f f t f s 2 0 114 "1009 1009" _null_ _null_ _null_ _null_ json_object_two_arg _null_ _null_ _null_ )); DESCR("map text arrayof key value pais to json object"); DATA(insert OID = 3176 ( to_json PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 114 "2283" _null_ _null_ _null_ _null_ to_json _null_ _null_ _null_ )); DESCR("map input to json"); *************** DESCR("get value from json as text with *** 4171,4176 **** --- 4171,4178 ---- 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_ )); *************** DATA(insert OID = 3204 ( json_to_record *** 4187,4196 **** DESCR("get record fields from a json object"); DATA(insert OID = 3205 ( json_to_recordset PGNSP PGUID 12 1 100 0 0 f f f f f t s 2 0 2249 "114 16" _null_ _null_ _null_ _null_ json_to_recordset _null_ _null_ _null_ )); 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_ )); --- 4189,4196 ---- DESCR("get record fields from a json object"); DATA(insert OID = 3205 ( json_to_recordset PGNSP PGUID 12 1 100 0 0 f f f f f t s 2 0 2249 "114 16" _null_ _null_ _null_ _null_ json_to_recordset _null_ _null_ _null_ )); 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"); /* 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_ )); *************** DESCR("I/O"); *** 4506,4511 **** --- 4506,4600 ---- 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 = 3920 ( 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 = 3921 ( 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 = 4052 ( gjsonb_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 5 0 16 "2281 2281 23 26 2281" _null_ _null_ _null_ _null_ gjsonb_consistent _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4053 ( gjsonb_union PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ gjsonb_union _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4054 ( gjsonb_compress PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gjsonb_compress _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4055 ( gjsonb_decompress PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2281 "2281" _null_ _null_ _null_ _null_ gjsonb_decompress _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4056 ( gjsonb_penalty PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gjsonb_penalty _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4057 ( gjsonb_picksplit PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 2281 "2281 2281" _null_ _null_ _null_ _null_ gjsonb_picksplit _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 4058 ( gjsonb_same PGNSP PGUID 12 1 0 0 0 f f f f t f i 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gjsonb_same _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 3479 ( gjsonb_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 3801 "2275" _null_ _null_ _null_ _null_ gjsonb_in _null_ _null_ _null_ )); + DESCR("GiST support"); + DATA(insert OID = 3480 ( gjsonb_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3801" _null_ _null_ _null_ _null_ gjsonb_out _null_ _null_ _null_ )); + DESCR("GiST 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 = 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"); + /* 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"); *** a/src/include/catalog/pg_type.h --- b/src/include/catalog/pg_type.h *************** DATA(insert OID = 3645 ( _tsquery PGNSP *** 606,611 **** --- 606,619 ---- 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 = 3801 ( gjsonb PGNSP PGUID -1 f b U f t \054 0 0 3800 gjsonb_in gjsonb_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ )); + DATA(insert OID = 3800 ( _gjsonb PGNSP PGUID -1 f b A f t \054 0 3801 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_ )); *** a/src/include/funcapi.h --- b/src/include/funcapi.h *************** extern void end_MultiFuncCall(PG_FUNCTIO *** 293,298 **** --- 293,307 ---- 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; \ *** a/src/include/utils/json.h --- b/src/include/utils/json.h *************** extern Datum json_populate_recordset(PG_ *** 64,67 **** --- 64,82 ---- 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 */ *** a/src/include/utils/jsonapi.h --- b/src/include/utils/jsonapi.h *************** typedef struct JsonSemAction *** 100,110 **** extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); /* ! * constructor 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. */ extern JsonLexContext *makeJsonLexContext(text *json, bool need_escapes); #endif /* JSONAPI_H */ --- 100,116 ---- extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); /* ! * 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 *** a/src/include/utils/jsonb.h --- b/src/include/utils/jsonb.h *************** *** 0 **** --- 1,245 ---- + /*------------------------------------------------------------------------- + * + * 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" + + #define JENTRY_POSMASK 0x0FFFFFFF + #define JENTRY_TYPEMASK (~(JENTRY_POSMASK | JENTRY_ISFIRST)) + + /* + * determined by the size of "endpos" (ie JENTRY_POSMASK) + */ + #define JSONB_MAX_STRING_LEN JENTRY_POSMASK + + /* + * it's not possible to get more than 2^28 items into an jsonb. + */ + #define JB_FLAG_ARRAY 0x40000000 + #define JB_FLAG_OBJECT 0x20000000 + #define JB_FLAG_SCALAR 0x10000000 + + #define JB_COUNT_MASK 0x0FFFFFFF + + #define JB_ISEMPTY(jbp_) (VARSIZE(jbp_) <= VARHDRSZ) + #define JB_ROOT_COUNT(jbp_) (JB_ISEMPTY(jbp_) ? 0 : ( *(uint32*)VARDATA(jbp_) & JB_COUNT_MASK)) + #define JB_ROOT_IS_OBJECT(jbp_) (JB_ISEMPTY(jbp_) ? 0 : ( *(uint32*)VARDATA(jbp_) & JB_FLAG_OBJECT)) + #define JB_ROOT_IS_ARRAY(jbp_) (JB_ISEMPTY(jbp_) ? 0 : ( *(uint32*)VARDATA(jbp_) & JB_FLAG_ARRAY)) + #define JB_ROOT_IS_SCALAR(jbp_) (JB_ISEMPTY(jbp_) ? 0 : ( *(uint32*)VARDATA(jbp_) & JB_FLAG_SCALAR)) + + #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/GiST index for jsonb, we choose to index both keys and + * values. The storage format is "text" values, with K, V, or N prepended to + * the string to indicate key, value, or null values. (As of 9.1 it might be + * better to store null values as nulls, but we'll keep it this way for on-disk + * compatibility.) + * + * jsonb Keys and elements are treated equivalently when serialized to text + * index storage. + */ + #define KEYELEMFLAG 'K' + #define VALFLAG 'V' + #define NULLFLAG 'N' + + #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; + + /* + * JEntry: there is one of these for each key _and_ value in a jsonb + * + * The position offset points to the _end_ so that we can get the length + * by subtraction from the previous entry. the ISFIRST flag lets us tell + * whether there is a previous entry. + */ + typedef struct + { + uint32 entry; + } JEntry; + + typedef struct + { + int32 vl_len_; /* varlena header (do not touch directly!) */ + /* header of jsonb object or array */ + /* array of JEntry follows */ + } Jsonb; + + struct JsonbValue + { + enum + { + jbvNull, + jbvString, + jbvNumeric, + jbvBool, + jbvArray, + jbvObject, + jbvBinary /* Binary form of jbvArray/jbvObject */ + } type; + + uint32 size; /* Estimation size of node (including + * subnodes) */ + + union + { + Numeric numeric; + bool boolean; + struct + { + uint32 len; + char *val; /* Not necessarily null-terminated */ + } string; + + struct + { + int nelems; + JsonbValue *elems; + bool scalar; /* Scalar actually shares representation with + * array */ + } array; + + struct + { + int npairs; + JsonbPair *pairs; + } object; /* Associative data structure */ + + struct + { + uint32 len; + char *data; + } binary; + }; + + }; + + struct JsonbPair + { + JsonbValue key; + JsonbValue value; + uint32 order; /* preserves order of pairs with equal keys */ + }; + + typedef struct ToJsonbState + { + JsonbValue v; + uint32 size; + struct ToJsonbState *next; + } ToJsonbState; + + typedef struct JsonbIterator + { + uint32 type; + uint32 nelems; + JEntry *array; + bool isScalar; + char *data; + char *buffer; /* unparsed buffer */ + int i; + + /* + * Enum members should be freely OR'ed with JB_FLAG_ARRAY/JB_FLAG_JSONB + * with possibility of decoding. See optimization in JsonbIteratorGet() + */ + enum + { + jbi_start = 0x00, + jbi_key = 0x01, + jbi_value = 0x02, + jbi_elem = 0x04 + } state; + + struct JsonbIterator *next; + } 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_extract_jsonb(PG_FUNCTION_ARGS); + extern Datum gin_extract_jsonb_query(PG_FUNCTION_ARGS); + extern Datum gin_consistent_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); + + /* GiST support functions */ + extern Datum gjsonb_consistent(PG_FUNCTION_ARGS); + extern Datum gjsonb_union(PG_FUNCTION_ARGS); + extern Datum gjsonb_compress(PG_FUNCTION_ARGS); + extern Datum gjsonb_decompress(PG_FUNCTION_ARGS); + extern Datum gjsonb_penalty(PG_FUNCTION_ARGS); + extern Datum gjsonb_picksplit(PG_FUNCTION_ARGS); + extern Datum gjsonb_same(PG_FUNCTION_ARGS); + /* Dummy GiST routines */ + extern Datum gjsonb_in(PG_FUNCTION_ARGS); + extern Datum gjsonb_out(PG_FUNCTION_ARGS); + + /* Support functions */ + extern int compareJsonbStringValue(const void *a, const void *b, void *arg); + extern int compareJsonbBinaryValue(char *a, char *b); + extern int compareJsonbValue(JsonbValue *a, JsonbValue *b); + extern JsonbValue *findUncompressedJsonbValueByValue(char *buffer, uint32 flags, + uint32 *lowbound, JsonbValue *key); + extern JsonbValue *findUncompressedJsonbValue(char *buffer, uint32 flags, + uint32 *lowbound, char *key, uint32 keylen); + extern JsonbValue *getJsonbValue(char *buffer, uint32 flags, int32 i); + extern JsonbValue *pushJsonbValue(ToJsonbState ** state, int r, JsonbValue *v); + extern JsonbIterator *JsonbIteratorInit(char *buffer); + extern int JsonbIteratorGet(JsonbIterator **it, JsonbValue *v, bool skipNested); + extern Jsonb *JsonbValueToJsonb(JsonbValue *v); + + /* jsonb.c support function */ + extern char *JsonbToCString(StringInfo out, char *in, int estimated_len); + + #endif /* __JSONB_H__ */ *** a/src/include/utils/numeric.h --- b/src/include/utils/numeric.h *************** typedef struct NumericData *Numeric; *** 58,62 **** --- 58,63 ---- 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 *** a/src/test/regress/data/jsonb.data --- b/src/test/regress/data/jsonb.data *************** *** 0 **** --- 1,1005 ---- + {"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":["foo", "bar", "baz"]} + {"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/jsonb.out b/src/test/regress/expected/jsonb.out new file mode 100644 *** a/src/test/regress/expected/jsonb.out --- b/src/test/regress/expected/jsonb.out *************** *** 0 **** --- 1,1976 ---- + -- 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"]'), + ('object','{"field1":"val1","field2":"val2","field3":null}'); + SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; + ERROR: cannot call jsonb_object_field on a scalar + SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; + ERROR: cannot call jsonb_object_field 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 on a scalar + SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; + ERROR: cannot call jsonb_object_field_text 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 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 on an object + 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 + (3 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) + + -- 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 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 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"}' ?& '{}'::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 gist(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 ? '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 GiST index scans too) + CREATE INDEX jidx_array ON testjsonb USING gist((j->'array')); + SELECT 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 1 + (1 row) + + RESET enable_seqscan; + DROP INDEX jidx; + DROP INDEX jidx_array; + 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 ? '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 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 1 + (1 row) + + RESET enable_seqscan; + SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; + count + ------- + 4784 + (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 + age | 2 + array | 1 + (24 rows) + + -- sort/hash + SELECT count(distinct j) FROM testjsonb; + count + ------- + 887 + (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 + ------- + 887 + (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 + ------- + 887 + (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) + + 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 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 *** a/src/test/regress/expected/jsonb_1.out --- b/src/test/regress/expected/jsonb_1.out *************** *** 0 **** --- 1,1976 ---- + -- 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"]'), + ('object','{"field1":"val1","field2":"val2","field3":null}'); + SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'scalar'; + ERROR: cannot call jsonb_object_field on a scalar + SELECT test_json -> 'x' FROM test_jsonb WHERE json_type = 'array'; + ERROR: cannot call jsonb_object_field 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 on a scalar + SELECT test_json ->> 'field2' FROM test_jsonb WHERE json_type = 'array'; + ERROR: cannot call jsonb_object_field_text 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 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 on an object + 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 + (3 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) + + -- 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 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 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"}' ?& '{}'::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 gist(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 ? '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 GiST index scans too) + CREATE INDEX jidx_array ON testjsonb USING gist((j->'array')); + SELECT 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 1 + (1 row) + + RESET enable_seqscan; + DROP INDEX jidx; + DROP INDEX jidx_array; + 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 ? '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 1 from testjsonb WHERE j->'array' ? 'bar'; + ?column? + ---------- + 1 + (1 row) + + RESET enable_seqscan; + SELECT count(*) FROM (SELECT (jsonb_each(j)).key FROM testjsonb) AS wow; + count + ------- + 4784 + (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 + age | 2 + array | 1 + (24 rows) + + -- sort/hash + SELECT count(distinct j) FROM testjsonb; + count + ------- + 887 + (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 + ------- + 887 + (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 + ------- + 887 + (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) + + 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 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) + *** a/src/test/regress/expected/opr_sanity.out --- b/src/test/regress/expected/opr_sanity.out *************** ORDER BY 1, 2, 3; *** 1108,1118 **** --- 1108,1122 ---- 783 | 7 | @> 783 | 8 | <@ 783 | 9 | &<| + 783 | 9 | ? 783 | 10 | <<| 783 | 10 | <^ + 783 | 10 | ?| 783 | 11 | >^ + 783 | 11 | ?& 783 | 11 | |>> 783 | 12 | |&> + 783 | 13 | <@ 783 | 13 | ~ 783 | 14 | @ 783 | 15 | <-> *************** ORDER BY 1, 2, 3; *** 1127,1132 **** --- 1131,1140 ---- 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 7 | @> + 2742 | 9 | ? + 2742 | 10 | ?| + 2742 | 11 | ?& 4000 | 1 | << 4000 | 1 | ~<~ 4000 | 2 | &< *************** ORDER BY 1, 2, 3; *** 1149,1155 **** 4000 | 15 | > 4000 | 16 | @> 4000 | 18 | = ! (67 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing --- 1157,1163 ---- 4000 | 15 | > 4000 | 16 | @> 4000 | 18 | = ! (75 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** test: event_trigger *** 98,105 **** # ---------- # 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 ! # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, --- 98,104 ---- # ---------- # 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 jsonb indirect_toast # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** test: xmlmap *** 122,127 **** --- 122,128 ---- test: functional_deps test: advisory_lock test: json + test: jsonb test: indirect_toast test: plancache test: limit diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql new file mode 100644 *** a/src/test/regress/sql/jsonb.sql --- b/src/test/regress/sql/jsonb.sql *************** *** 0 **** --- 1,463 ---- + -- 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"]'), + ('object','{"field1":"val1","field2":"val2","field3":null}'); + + 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 ->> 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}'; + + -- 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 1 from testjsonb WHERE j->'array' ? 'bar'; + + 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"}' ?& '{}'::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 gist(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 ? '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 GiST index scans too) + CREATE INDEX jidx_array ON testjsonb USING gist((j->'array')); + SELECT 1 from testjsonb WHERE j->'array' ? 'bar'; + + RESET enable_seqscan; + + DROP INDEX jidx; + DROP INDEX jidx_array; + 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 ? '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 1 from testjsonb WHERE j->'array' ? 'bar'; + + 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}'; + + 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';