*** 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 ****
!
! 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 ).
! json> and 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 ----
+
+
+
+ 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 PostgreSQL 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 jsonb 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.
+
+
+
+ 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.
+
+
+
+ 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 .
+
+
+
+ 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';