From 3af2d71cf60d6c32e7a642c9ae5fa1b756d36c3a Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Mon, 13 Sep 2021 16:55:21 -0400 Subject: [PATCH 1/4] SQL/JSON functions Common SQL/JSON clauses SQL/JSON constructors IS JSON predicate SQL/JSON query functions SQL/JSON functions for json type GUC sql_json --- doc/src/sgml/config.sgml | 19 + doc/src/sgml/func.sgml | 2027 +++++++++++++++++ doc/src/sgml/keywords/sql2016-02-reserved.txt | 3 + src/backend/executor/execExpr.c | 349 ++- src/backend/executor/execExprInterp.c | 726 ++++++ src/backend/jit/llvm/llvmjit_expr.c | 18 + src/backend/jit/llvm/llvmjit_types.c | 3 + src/backend/nodes/copyfuncs.c | 433 ++++ src/backend/nodes/equalfuncs.c | 293 +++ src/backend/nodes/makefuncs.c | 103 + src/backend/nodes/nodeFuncs.c | 427 ++++ src/backend/nodes/outfuncs.c | 141 ++ src/backend/nodes/readfuncs.c | 177 ++ src/backend/optimizer/path/costsize.c | 3 +- src/backend/optimizer/util/clauses.c | 78 + src/backend/parser/gram.y | 767 ++++++- src/backend/parser/parse_collate.c | 4 + src/backend/parser/parse_expr.c | 1471 ++++++++++++ src/backend/parser/parse_target.c | 37 + src/backend/parser/parser.c | 16 + src/backend/utils/adt/format_type.c | 9 + src/backend/utils/adt/formatting.c | 45 +- src/backend/utils/adt/json.c | 572 ++++- src/backend/utils/adt/jsonb.c | 352 ++- src/backend/utils/adt/jsonb_util.c | 24 +- src/backend/utils/adt/jsonfuncs.c | 70 +- src/backend/utils/adt/jsonpath.c | 257 +++ src/backend/utils/adt/jsonpath_exec.c | 350 ++- src/backend/utils/adt/ruleutils.c | 437 +++- src/backend/utils/misc/guc.c | 19 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/backend/utils/misc/queryjumble.c | 69 + src/include/catalog/pg_aggregate.dat | 22 + src/include/catalog/pg_proc.dat | 70 + src/include/executor/execExpr.h | 81 + src/include/executor/executor.h | 2 + src/include/nodes/makefuncs.h | 10 + src/include/nodes/nodes.h | 24 + src/include/nodes/parsenodes.h | 203 ++ src/include/nodes/primnodes.h | 222 ++ src/include/parser/kwlist.h | 22 + src/include/utils/formatting.h | 4 + src/include/utils/json.h | 26 + src/include/utils/jsonb.h | 47 + src/include/utils/jsonfuncs.h | 7 + src/include/utils/jsonpath.h | 33 + src/interfaces/ecpg/preproc/parse.pl | 2 + src/interfaces/ecpg/preproc/parser.c | 14 + src/test/regress/expected/json_sqljson.out | 15 + src/test/regress/expected/jsonb.out | 130 ++ src/test/regress/expected/jsonb_sqljson.out | 1018 +++++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/expected/sqljson.out | 1352 +++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/json_sqljson.sql | 11 + src/test/regress/sql/jsonb.sql | 38 + src/test/regress/sql/jsonb_sqljson.sql | 317 +++ src/test/regress/sql/opr_sanity.sql | 6 +- src/test/regress/sql/sqljson.sql | 467 ++++ 59 files changed, 13151 insertions(+), 300 deletions(-) create mode 100644 src/test/regress/expected/json_sqljson.out create mode 100644 src/test/regress/expected/jsonb_sqljson.out create mode 100644 src/test/regress/expected/sqljson.out create mode 100644 src/test/regress/sql/json_sqljson.sql create mode 100644 src/test/regress/sql/jsonb_sqljson.sql create mode 100644 src/test/regress/sql/sqljson.sql diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index ef0e2a7746..1b9a552ec8 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -9500,6 +9500,25 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' + + sql_json (enum) + json + jsonb + + sql_json configuration parameter + + + + + Valid values are json and jsonb. + Specifies what PostgreSQL type is used + as an implementation of SQL type JSON. + When sql_json is set to jsonb, + PostgreSQL type json can be + accessed using explicit qualification pg_catalog.json. + + + diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 78812b2dbe..a75b92bdb9 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17456,6 +17456,1924 @@ $.* ? (@ like_regex "^\\d+$") + + + SQL/JSON Functions and Expressions + + SQL/JSON + functions and expressions + + + + To provide native support for JSON data types within the SQL environment, + PostgreSQL implements the + SQL/JSON data model. + This model comprises sequences of items. Each item can hold SQL scalar values, + with an additional SQL/JSON null value, and composite data structures that use JSON + arrays and objects. + + + + SQL/JSON enables you to handle JSON data alongside regular SQL data, + with transaction support: + + + + + + Upload JSON data into a relational database and store it in + regular SQL columns as character or binary strings. + + + + + Generate JSON objects and arrays from relational data. + + + + + Query JSON data using SQL/JSON query functions and SQL/JSON path + language expressions. + + + + + + All SQL/JSON functions fall into one of two groups. + Constructor functions + generate JSON data from values of SQL types. + Query functions + evaluate SQL/JSON path language expressions against JSON values + and produce values of SQL/JSON types, which are converted to SQL types. + + + + Producing JSON Content + + + PostgreSQL provides several functions + that generate JSON data. Taking values of SQL types as input, these + functions construct JSON objects, JSON arrays or JSON scalars represented + as json or jsonb types, SQL character or binary strings. + + + + + + JSON + + + + + JSON_SCALAR + + + + + JSON_OBJECT + + + + + JSON_OBJECTAGG + + + + + JSON_ARRAY + + + + + JSON_ARRAYAGG + + + + + + <literal>JSON</literal> + json + +JSON ( + expression FORMAT JSON ENCODING UTF8 + { WITH | WITHOUT } UNIQUE KEYS + RETURNING json_data_type +) + + + + Description + + + JSON function generates a JSON + from a text data. + + + + + Parameters + + + + expression FORMAT JSON ENCODING UTF8 + + + + String expression that provides the JSON text data. + Accepted any character strings (text, char, etc.) + or binary strings (bytea) in UTF8 encoding. + For null input, SQL null value is returned. + + + The optional FORMAT clause is provided to conform + to the SQL/JSON standard. + + + + + + { WITH | WITHOUT } UNIQUE KEYS + + + + Defines whether duplicate keys are allowed: + + + + WITHOUT + + + Default. The constructed + JSON object can contain duplicate keys. + + + + + WITH + + + Duplicate keys are not allowed. + If the input data contains duplicate keys, an error is returned. + + + + + + Optionally, you can add the KEYS keyword for + semantic clarity. + + + + + + RETURNING json_data_type + + + + The output clause that specifies the type (json or + jsonb) of the generated JSON. + + + + + + + + Notes + + Alternatively, you can construct JSON values simply + using PostgreSQL-specific casts to + json and jsonb types. + + + + Examples + + Construct a JSON the provided strings: + + +SELECT JSON('{ "a" : 123, "b": [ true, "foo" ], "a" : "bar" }'); + json +-------------------------------------------------- + { "a" : 123, "b": [ true, "foo" ], "a" : "bar" } +(1 row) + + +SELECT JSON('{"a": 123, "b": [true, "foo"], "a": "bar"}' RETURNING jsonb); + json +---------------------------------- + {"a": "bar", "b": [true, "foo"]} +(1 row) + +SELECT JSON('{"a": 123, "b": [true, "foo"], "a": "bar"}' WITH UNIQUE KEYS); +ERROR: duplicate JSON object key value + + + + + + <literal>JSON_SCALAR</literal> + json_scalar + + +JSON_SCALAR ( + expression + RETURNING json_data_type +) + + + + Description + + + JSON_SCALAR function generates a scalar + JSON from a SQL data. + + + + + Parameters + + + + expression + + + + Expression that provides the data for constructing a + JSON. + For null input, SQL null + (not a JSON null) value is returned. + For any scalar other than a number, a Boolean, the text representation + will be used, with escaping as necessary to make it a valid + JSON string value. + For details, see + to_json()/to_jsonb() + in . + + + + + + RETURNING json_data_type + + + + The output clause that specifies the type (json or + jsonb) of the generated JSON scalar. + + + + + + + + Notes + + Alternatively, you can construct JSON objects by + using PostgreSQL-specific + to_json()/to_jsonb() functions. + See for details. + + + + Examples + + Construct a JSON from the provided values various types: + + +SELECT JSON_SCALAR(123.45); + json_scalar +------------- + 123.45 +(1 row) + +SELECT JSON_SCALAR('123'); + json_scalar +------------- + "123" +(1 row) + +SELECT JSON_SCALAR(true); + json_scalar +------------- + true +(1 row) + + + + + + <literal>JSON_OBJECT</literal> + json_object + + +JSON_OBJECT ( + { key_expression { VALUE | ':' } + value_expression FORMAT JSON ENCODING UTF8 }, ... + { NULL | ABSENT } ON NULL + { WITH | WITHOUT } UNIQUE KEYS + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + + + + Description + + + The JSON_OBJECT function generates a JSON + object from SQL or JSON data. + + + + + Parameters + + + + + + key_expression { VALUE | ':' } + value_expression FORMAT JSON ENCODING UTF8 + + + + + The input clause that provides the data for constructing a JSON object: + + + + + key_expression is a scalar expression + defining the JSON key, which is implicitly + converted to the text type. + The provided expression cannot be NULL or + belong to a type that has a cast to json. + + + + + value_expression is an expression + that provides the input for the JSON value. + + + + + The optional FORMAT clause is provided to + conform to the SQL/JSON standard. + + + + + You must use a colon or the VALUE keyword as a + delimiter between the key and the value. Multiple key/value pairs are + separated by commas. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed + JSON object: + + + + NULL + + + Default. NULL values are allowed. + + + + + ABSENT + + + If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE KEYS + + + Defines whether duplicate keys are allowed: + + + + WITHOUT + + + Default. The constructed + JSON object can contain duplicate keys. + + + + + WITH + + + Duplicate keys are not allowed. + If the input data contains duplicate keys, an error is returned. + This check is performed before removing JSON items with NULL values. + + + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the type of the generated JSON object. + For details, see . + + + + + + + + + Notes + Alternatively, you can construct JSON objects by using + PostgreSQL-specific json_build_object()/ + jsonb_build_object() functions. + See for details. + + + + + Examples + + Construct a JSON object from the provided key/value pairs of various types: + + +SELECT JSON_OBJECT( +-- scalar JSON types + 'key1': 'string', + 'key2': '[1, 2]', + 'key3' VALUE 123, -- alternative syntax for key-value delimiter + 'key4': NULL, +-- other types + 'key5': ARRAY[1, 2, 3], -- postgres array + 'key6': jsonb '{"a": ["b", 1]}', -- composite json/jsonb + 'key7': date '2017-09-30', -- datetime type + 'key8': row(1, 'a'), -- row type + 'key9': '[1, 2]' FORMAT JSON, -- same value as for key2, but with FORMAT +-- key can be an expression + 'key' || 'last' : TRUE +ABSENT ON NULL) AS json; + json +---------------------------------------------------- +{"key1" : "string", "key2" : "[1, 2]", "key3" : 123, + "key5" : [1,2,3], "key6" : {"a": ["b", 1]}, + "key7" : "2017-09-30", "key8" : {"f1":1,"f2":"a"}, + "key9" : [1, 2], "keylast" : true} +(1 row) + + + + From the films table, select some data + about the films distributed by Paramount Pictures + (did = 103) and return JSON objects: + + +SELECT +JSON_OBJECT( + 'code' VALUE f.code, + 'title' VALUE f.title, + 'did' VALUE f.did +) AS paramount +FROM films AS f +WHERE f.did = 103; + paramount +---------------------------------------------------- +{"code" : "P_301", "title" : "Vertigo", "did" : 103} +{"code" : "P_302", "title" : "Becket", "did" : 103} +{"code" : "P_303", "title" : "48 Hrs", "did" : 103} +(3 rows) + + + + + + <literal>JSON_OBJECTAGG</literal> + json_objectagg + + +JSON_OBJECTAGG ( + { key_expression { VALUE | ':' } value_expression } + { NULL | ABSENT } ON NULL + { WITH | WITHOUT } UNIQUE KEYS + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + + + + + Description + + + The JSON_OBJECTAGG function aggregates the provided data + into a JSON object. You can use this function to combine values + stored in different table columns into pairs. If you specify a GROUP BY + or an ORDER BY clause, this function returns a separate JSON object + for each table row. + + + + + Parameters + + + + + key_expression { VALUE | ':' } value_expression + + + + + The input clause that provides the data to be aggregated as a JSON object: + + + + + key_expression is a scalar expression + defining the JSON key, which is implicitly + converted to the text type. + The provided expression cannot be NULL or + belong to a type that has a cast to json. + + + + + value_expression is an expression that + provides the input for the JSON value preceded + by its type. + For JSON scalar types, you can omit the type. + + + + The input value of the bytea type must be stored in UTF8 + and contain a valid UTF8 string. Otherwise, an error occurs. + PostgreSQL currently supports only UTF8. + + + + + + You must use a colon or the VALUE keyword as a delimiter between + keys and values. Multiple key/value pairs are separated by commas. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed + JSON object: + + + + NULL + + + Default. NULL values are allowed. + + + + + ABSENT + + + If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE KEYS + + + Defines whether duplicate keys are allowed: + + + + WITHOUT + + + Default. The constructed + JSON object can contain duplicate keys. + + + + + WITH + + + Duplicate keys are not allowed. + If the input data contains duplicate keys, an error is returned. + This check is performed before removing JSON items with NULL values. + + + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the type of the generated JSON object. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON objects by using + PostgreSQL-specific json_object_agg()/ + jsonb_object_agg() aggregate functions. + See for details. + + + + + Examples + + + For films with did = 103, aggregate key/value pairs + of film genre (f.kind) and title (f.title) + into a single object: + + +SELECT +JSON_OBJECTAGG( + f.kind VALUE f.title) + AS films_list +FROM films AS f +where f.did = 103; + films_list +---------------------------------------------------- +{ "Action" : "Vertigo", "Drama" : "Becket", "Action" : "48 Hrs" } + + + + Return the same object as jsonb. Note that only a single film of + the action genre is included as the jsonb type does not allow duplicate keys. + + +SELECT +JSON_OBJECTAGG( + f.kind VALUE f.title + RETURNING jsonb) +AS films_list +FROM films AS f +where f.did = 103; + films_list +---------------------------------------------------- +{"Drama": "Becket", "Action": "48 Hrs"} + + + + Return objects of film titles and length, grouped by the film genre: + + +SELECT + f.kind, + JSON_OBJECTAGG( + f.title VALUE f.len +) AS films_list +FROM films AS f +GROUP BY f.kind; + + kind | films_list +-------------+---------------------------------- +Musical | { "West Side Story" : "02:32:00", "The King and I" : "02:13:00", "Bed Knobs and Broomsticks" : "01:57:00" } +Romantic | { "The African Queen" : "01:43:00", "Une Femme est une Femme" : "01:25:00", "Storia di una donna" : "01:30:00" } +Comedy | { "Bananas" : "01:22:00", "There's a Girl in my Soup" : "01:36:00" } +Drama | { "The Third Man" : "01:44:00", "Becket" : "02:28:00", "War and Peace" : "05:57:00", "Yojimbo" : "01:50:00", "Das Boot" : "02:29:00" } +Action | { "Vertigo" : "02:08:00", "48 Hrs" : "01:37:00", "Taxi Driver" : "01:54:00", "Absence of Malice" : "01:55:00" } +(5 rows) + + + + + + <literal>JSON_ARRAY</literal> + json_array + + +JSON_ARRAY ( + { value_expression FORMAT JSON } , ... + { NULL | ABSENT } ON NULL + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + +JSON_ARRAY ( + query_expression + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + + + + Description + + + The JSON_ARRAY function constructs a JSON array from + the provided SQL or JSON data. + + + + + Parameters + + + + + value_expression + + + + + The input clause that provides the data for constructing a JSON array. + The value_expression is an expression + that provides the input for the JSON value preceded by its type. + For JSON scalar types, you can omit the type. + + + + The input value of the bytea type must be stored in UTF8 + and contain a valid UTF8 string. Otherwise, an error occurs. + PostgreSQL currently supports only UTF8. + + + + + + + + + query_expression + + + + An SQL query that provides the data for constructing a JSON array. + The query must return a single column that holds the values to be + used in the array. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the generated JSON array: + + + + NULL + + + NULL values are allowed. + + + + + ABSENT + + + Default. If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + This clause is only supported for arrays built from an explicit list of values. + If you are using an SQL query to generate an array, NULL values are always + omitted. + + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the return type of the constructed JSON array. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON arrays by using + PostgreSQL-specific json_build_array()/ + jsonb_build_array() functions. + See for details. + + + + + Examples + + From the films table, select some data + about the films distributed by Paramount Pictures + (did = 103) and return JSON arrays: + + +SELECT +JSON_ARRAY( + f.code, + f.title, + f.did +) AS films +FROM films AS f +WHERE f.did = 103; + films +---------------------------------------------------- +["code" : "P_301", "title" : "Vertigo", "did" : 103] +["code" : "P_302", "title" : "Becket", "did" : 103] +["code" : "P_303", "title" : "48 Hrs", "did" : 103] +(3 rows) + + + Construct a JSON array from the list of film titles returned from the + films table by a subquery: + + +SELECT +JSON_ARRAY( + SELECT + f.title +FROM films AS f +where f.did = 103) +AS film_titles; + film_titles +---------------------------------------------------- +["Vertigo", "Becket", "48 Hrs"] +(1 row) + + + + + + <literal>JSON_ARRAYAGG</literal> + json_arrayagg + + +JSON_ARRAYAGG ( + value_expression + ORDER BY sort_expression + { NULL | ABSENT } ON NULL + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + + + + + Description + + + The JSON_ARRAYAGG function aggregates the provided SQL + or JSON data into a JSON array. + + + + + Parameters + + + + + value_expression + + + + + The input clause that provides the input data to be aggregated as + a JSON array. + The value_expression can be a value or a query + returning the values to be used as input in array construction. + You can provide multiple input values separated by commas. + + + + + + + ORDER BY + + + + Sorts the input data to be aggregated as a JSON array. + For details on the exact syntax of the ORDER BY clause, see . + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed array: + + + + NULLNULL values are allowed. + + + + + ABSENT (default) — NULL + values are omitted from the generated array. + + + + + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the return type of the constructed JSON array. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON arrays by using + PostgreSQL-specific json_agg()/ + jsonb_agg() functions. + See for details. + + + + + Examples + + Construct an array of film titles sorted in alphabetical order: + + +SELECT +JSON_ARRAYAGG( + f.title +ORDER BY f.title ASC) AS film_titles +FROM films AS f; + film_titles +---------------------------------------------------- +["48 Hrs", "Absence of Malice", "Bananas", "Becket", "Bed Knobs and Broomsticks", "Das Boot", "Storia di una donna", "Taxi Driver", "The African Queen", "The King and I", "There's a Girl in my Soup", "The Third Man", "Une Femme est une Femme", "Vertigo", "War and Peace", "West Side Story", "Yojimbo"] +(1 row) + + + + + + + Querying JSON + + + SQL/JSON query functions evaluate SQL/JSON path language expressions + against JSON values, producing values of SQL/JSON types, which are + converted to SQL types. All SQL/JSON query functions accept several + common clauses described in . + For details on the SQL/JSON path language, + see . + + + + + + IS JSON + + + + + JSON_EXISTS + + + + + JSON_VALUE + + + + + JSON_QUERY + + + + + + In some usage examples for these functions, + the following small table storing some JSON data will be used: + +CREATE TABLE my_films ( + js text ); + +INSERT INTO my_films VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ + { "title" : "Bananas", + "director" : "Woody Allen"}, + { "title" : "The Dinner Game", + "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [ + { "title" : "Psycho", + "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [ + { "title" : "Vertigo", + "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [ + { "title" : "Yojimbo", + "director" : "Akira Kurosawa" } ] } + ] }'); + + + + + <literal>JSON_EXISTS</literal> + json_exists + + +JSON_EXISTS ( + context_item, path_expression PASSING { value AS varname } , ... + RETURNING data_type + { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR +) + + + + Description + + + JSON_EXISTS function checks whether the provided + JSON path expression can return any SQL/JSON items. + + + + + Parameters + + + + context_item, path_expression PASSING { value AS varname } , ... + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + See for details. + + + + + + + RETURNING data_type + + + + The output clause that specifies the data type of the returned value. + The specified data type should have a cast from a boolean + type, which is returned by default. + + + + + + + { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR + + + + Defines the return value if an error occurs. The default value is FALSE. + + + + + + + + + Examples + + + Check whether the provided jsonb data contains a + key/value pair with the key1 key, and its value + contains an array with one or more elements bigger than 2: + + +SELECT JSON_EXISTS(jsonb '{"key1": [1,2,3]}', 'strict $.key1[*] ? (@ > 2)'); + json_exists +------------- + t +(1 row) + + + + Note the difference between strict and lax modes + if the required item does not exist: + + +-- Strict mode with ERROR on ERROR clause +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR); +ERROR: Invalid SQL/JSON subscript +(1 row) + + + +-- Lax mode +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'lax $.a[5]' ERROR ON ERROR); + json_exists +------------- + f +(1 row) + + + +-- Strict mode using the default value for the ON ERROR clause +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]'); + json_exists +------------- + f +(1 row) + + + + + + + <literal>JSON_VALUE</literal> + json_value + + +JSON_VALUE ( + context_item, path_expression PASSING { value AS varname } , ... + RETURNING data_type + { ERROR | NULL | DEFAULT expression } ON EMPTY + { ERROR | NULL | DEFAULT expression } ON ERROR +) + + + + Description + + + JSON_VALUE function extracts a value from the provided + JSON data and converts it to an SQL scalar. + If the specified JSON path expression returns more than one + SQL/JSON item, an error occurs. To extract + an SQL/JSON array or object, use . + + + + + Parameters + + + + + + context_item, path_expression PASSING { value AS varname } , ... + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + For details, see . + + + + + + + RETURNING data_type + + + + The output clause that specifies the data type of the returned value. + Out of the box, PostgreSQL + supports the following types: json, jsonb, + bytea, and character string types (text, char, + varchar, and nchar). + The extracted value must be a single SQL/JSON scalar item + and have a cast to the specified type. Otherwise, an error occurs. + By default, JSON_VALUE returns a string + of the text type. + + + + + + + { ERROR | NULL | DEFAULT expression } ON EMPTY + + + + Defines the return value if no JSON value is found. The default is + NULL. If you use + DEFAULT expression, + the provided expression is + evaluated and cast to the type specified in the RETURNING clause. + + + + + + + { ERROR | NULL | DEFAULT expression } ON ERROR + + + + Defines the return value if an unhandled error occurs. The default is + NULL. If you use + DEFAULT expression, + the provided expression is + evaluated and cast to the type specified in the RETURNING clause. + + + + + + + + + Examples + + + Extract an SQL/JSON value and return it as an SQL + scalar of the specified type. Note that + JSON_VALUE can only return a + single scalar, and the returned value must have a + cast to the specified return type: + + + +SELECT JSON_VALUE('"123.45"', '$' RETURNING float); + json_value +------------ + 123.45 +(1 row) + +SELECT JSON_VALUE('123.45', '$' RETURNING int ERROR ON ERROR); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE('"03:04 2015-02-01"', '$.datetime("HH24:MI YYYY-MM-DD")' RETURNING date); + json_value +------------ + 2015-02-01 +(1 row) + +SELECT JSON_VALUE('"123.45"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for integer: "123.45" + +SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item + + + + If the path expression returns an array, an object, or + multiple SQL/JSON items, an error is returned, as specified + in the ON ERROR clause: + + +SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '{"a": 1}', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 1 ON ERROR); +1 + + + + + + + <literal>JSON_QUERY</literal> + json_query + + +JSON_QUERY ( + context_item, path_expression PASSING { value AS varname } , ... + RETURNING data_type FORMAT JSON ENCODING UTF8 + { WITHOUT | WITH { CONDITIONAL | UNCONDITIONAL } } ARRAY WRAPPER + { KEEP | OMIT } QUOTES ON SCALAR STRING + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON EMPTY + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON ERROR +) + + + + Description + + + JSON_QUERY function extracts an SQL/JSON + array or object from JSON data. This function must return + a JSON string, so if the path expression returns a scalar or multiple SQL/JSON + items, you must wrap the result using the WITH WRAPPER clause. + To extract a single SQL/JSON value, you can use . + + + + + Parameters + + + + + + context_item, path_expression PASSING { value AS varname } , ... + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + For details, see . + + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the data type of the returned value. + For details, see . + + + + + + + { WITHOUT | WITH { CONDITIONAL | UNCONDITIONAL } } ARRAY WRAPPER + + + + Defines whether to wrap a returned sequence of SQL/JSON + items into a SQL/JSON array. + + + + WITHOUT WRAPPER + + + Do not wrap the result. + This is the default behavior if the WRAPPER + clause is omitted. + + + + + WITH UNCONDITIONAL WRAPPER + + + Always wrap the result. + + + + + WITH CONDITIONAL WRAPPER + + + Wrap the result if the path + expression returns anything other than a single + SQL/JSON array or object. + + + + + + Optionally, you can add the ARRAY keyword for semantic clarity. + + + You cannot use this clause together with the ON EMPTY clause. + + + + + + + + { KEEP | OMIT } QUOTES ON SCALAR STRING + + + + Defines whether to keep or omit quotes if a scalar string is returned. + By default, scalar strings are returned with quotes. Using this + clause together with the WITH WRAPPER clause is not allowed. + + + Optionally, you can add the ON SCALAR STRING keywords for semantic clarity. + + + + + + + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON EMPTY + + + + Defines the return value if no JSON value is found. The default is NULL. + If you use EMPTY ARRAY or EMPTY OBJECT, + an empty JSON array [] or object {} is returned, respectively. + If you use DEFAULT expression, + the provided expression is evaluated and cast + to the type specified in the RETURNING clause. + + + You cannot use this clause together with the WRAPPER clause. + + + + + + + { ERROR | NULL | EMPTY { ARRAY | OBJECT } | DEFAULT expression } ON ERROR + + + + Defines the return value if an unhandled error occurs. The default is NULL. + If you use EMPTY ARRAY or EMPTY OBJECT, + an empty JSON array [] or object {} are returned, respectively. + If you use DEFAULT expression, + the provided expression is evaluated and cast + to the type specified in the RETURNING clause. + + + + + + + + + Examples + + + Extract all film genres listed in the my_films table: + + +SELECT + JSON_QUERY(js, '$.favorites[*].kind' WITH WRAPPER ERROR ON ERROR) +FROM my_films; + json_query +------------ + ["comedy", "horror", "thriller", "drama"] +(1 row) + + + + Note that the same query will result in an error if you omit the + WITH WRAPPER clause, as it returns multiple SQL/JSON items: + + +SELECT + JSON_QUERY(js, '$.favorites[*].kind' ERROR ON ERROR) +FROM my_films; +ERROR: more than one SQL/JSON item + + + + Compare the effect of different WRAPPER clauses: + + +SELECT + js, + JSON_QUERY(js, 'lax $[*]') AS "without", + JSON_QUERY(js, 'lax $[*]' WITH WRAPPER) AS "with uncond", + JSON_QUERY(js, 'lax $[*]' WITH CONDITIONAL WRAPPER) AS "with cond" +FROM + (VALUES (jsonb '[]'), ('[1]'), ('[[1,2,3]]'), ('[{"a": 1}]'), ('[1, null, "2"]')) foo(js); + js | without | with uncond | with cond +----------------+-----------+----------------+---------------- + [] | (null) | (null) | (null) + [1] | 1 | [1] | [1] + [[1, 2, 3]] | [1, 2, 3] | [[1, 2, 3]] | [1, 2, 3] + [{"a": 1}] | {"a": 1} | [{"a": 1}] | {"a": 1} + [1, null, "2"] | (null) | [1, null, "2"] | [1, null, "2"] +(5 rows) + + +Compare quote handling for scalar types with and without the OMIT QUOTES clause: + + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + + + + + + <literal>IS JSON</literal> + is_json + + +expression + IS NOT JSON + { VALUE | SCALAR | ARRAY | OBJECT } + { WITH | WITHOUT } UNIQUE KEYS + + + + Description + + + The IS JSON predicate tests whether the provided value is valid + JSON data. If you provide a specific JSON data type as a parameter, + you can check whether the value belongs to this type. + You can also use this predicate in the IS NOT JSON form. + The return values are: + + + + t if the value satisfies the specified condition. + + + + + f if the value does not satisfy the specified condition. + + + + + + + + Parameters + + + + + + expression + + + + + The input clause defining the value to test. You can provide the values + of json, jsonb, + bytea, or character string types. + + + + + + + VALUE | SCALAR | ARRAY | OBJECT + + + + + Specifies the JSON data type to test for: + + + + VALUE (default) — any JSON type. + + + + + SCALARJSON number, string, or boolean. + + + + + ARRAYJSON array. + + + + + OBJECTJSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE KEYS + + + Defines whether duplicate keys are allowed: + + + + WITHOUT (default) — the + JSON object can contain duplicate keys. + + + + + WITH — duplicate keys are not allowed. + If the input data contains duplicate keys, it is considered to be invalid JSON. + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + + + Examples + + + Compare the result returned by the IS JSON + predicate for different data types: + + +SELECT + js, + js IS JSON "is json", + js IS NOT JSON "is not json", + js IS JSON SCALAR "is scalar", + js IS JSON OBJECT "is object", + js IS JSON ARRAY "is array" +FROM + (VALUES ('123'), ('"abc"'), ('{"a": "b"}'), ('[1,2]'), ('abc')) foo(js); + + js | is json | is not json | is scalar | is object | is array +------------+---------+-------------+-----------+-----------|------------- + 123 | t | f | t | f | f + "abc" | t | f | t | f | f + {"a": "b"} | t | f | f | t | f + [1,2] | t | f | f | f | t + abc | f | t | f | f | f +(5 rows) + + + + + + + + + Serializing JSON data + + + + JSON_SERIALIZE + + + + + + <literal>JSON_SERIALAIZE</literal> + json_serialize + + +JSON_SERIALIZE ( + expression FORMAT JSON ENCODING UTF8 + RETURNING data_type FORMAT JSON ENCODING UTF8 +) + + + + Description + + + JSON_SERIALIZE function transforms a SQL/JSON value + into a character or binary string. + + + + + Parameters + + + + expression FORMAT JSON ENCODING UTF8 + + + + JSON typed expression that provides a data for + serialization. Accepted JSON types (json and + jsonb), any character string types (text, + char, etc.), binary strings (bytea) in + UTF8 encoding. + For null input, null value is returned. + + + The optional FORMAT clause is provided to conform + to the SQL/JSON standard. + + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the target character or binary string + type (text, char, bytea, etc.). + + + + + + + + Notes + + Alternatively, you can construct JSON values simply + using PostgreSQL-specific casts to + json and jsonb types. + + + + Examples + + Construct a JSON the provided strings: + + +SELECT JSON_SERIALIZE(JSON_SCALAR('foo')); + json_serialize +---------------- + "foo" +(1 row) + +SELECT JSON_SERIALIZE('{"foo": "bar", "baz": [1, 2, 3]}' RETURNING bytea); + json_serialize +-------------------------------------------------------------------- + \x7b22666f6f223a2022626172222c202262617a223a205b312c20322c20335d7d +(1 row) + + + + + + + + + SQL/JSON Common Clauses + + + SQL/JSON Input Clause + + + + + context_item, path_expression + PASSING { value AS varname } , ... + + + + The input clause specifies the JSON data to query and + the exact query path to be passed to SQL/JSON query functions: + + + + + The context_item is the JSON data to query. + + + + + The path_expression is an SQL/JSON path + expression that specifies the items to be retrieved from the JSON + data. For details on path expression syntax, see + . + + + + + The optional PASSING clause provides the values for + the named variables used in the SQL/JSON path expression. + + + + + The input clause is common for all SQL/JSON query functions. + + + + + + + + + SQL/JSON Output Clause + + + + + RETURNING data_type FORMAT JSON ENCODING UTF8 + + + + The output clause that specifies the return type of the generated + JSON object. Out of the box, PostgreSQL + supports the following types: json, jsonb, + bytea, and character string types (text, char, + varchar, and nchar). + To use other types, you must create the CAST from json for this type. + By default, the json type is returned. + + + The optional FORMAT clause is provided to conform to the SQL/JSON standard. + + + The output clause is common for both constructor and query SQL/JSON functions. + + + + + + + + + @@ -19596,6 +21514,115 @@ SELECT NULLIF(value, '(none)') ... No + + + + + json_agg_strict + + json_agg_strict ( anyelement ) + json + + + + jsonb_agg_strict + + jsonb_agg_strict ( anyelement ) + jsonb + + + Collects all the input values, skipping nulls, into a JSON array. + Values are converted to JSON as per to_json + or to_jsonb. + + No + + + + + + json_object_agg_strict + + json_object_agg_strict ( + key "any", + value "any" ) + json + + + + jsonb_object_agg_strict + + jsonb_object_agg_strict ( + key "any", + value "any" ) + jsonb + + + Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as + per to_json or to_jsonb. + Null values are skipped, keys can not be null. + + No + + + + + + json_object_agg_unique + + json_object_agg_unique ( + key "any", + value "any" ) + json + + + + jsonb_object_agg_unique + + jsonb_object_agg_unique ( + key "any", + value "any" ) + jsonb + + + Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as + per to_json or to_jsonb. + Values can be null, but not keys. + In case of duplicate keys error is thrown. + + No + + + + + + json_object_agg_unique_strict + + json_object_agg_unique_strict ( + key "any", + value "any" ) + json + + + + jsonb_object_agg_unique_strict + + jsonb_object_agg_unique_strict ( + key "any", + value "any" ) + jsonb + + + Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as + per to_json or to_jsonb. + Null values are skipped, keys can not be null. + In case of duplicate keys error is thrown. + + No + diff --git a/doc/src/sgml/keywords/sql2016-02-reserved.txt b/doc/src/sgml/keywords/sql2016-02-reserved.txt index ae11012388..7ba4208398 100644 --- a/doc/src/sgml/keywords/sql2016-02-reserved.txt +++ b/doc/src/sgml/keywords/sql2016-02-reserved.txt @@ -156,12 +156,15 @@ INTERVAL INTO IS JOIN +JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG JSON_QUERY +JSON_SCALAR +JSON_SERIALIZE JSON_TABLE JSON_TABLE_PRIMITIVE JSON_VALUE diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 81b9d87bad..efcfde8c95 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -47,6 +47,9 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -85,6 +88,40 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, bool nullcheck); +static ExprState * +ExecInitExprInternal(Expr *node, PlanState *parent, ParamListInfo ext_params, + Datum *caseval, bool *casenull) +{ + ExprState *state; + ExprEvalStep scratch = {0}; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + state->parent = parent; + state->ext_params = ext_params; + state->innermost_caseval = caseval; + state->innermost_casenull = casenull; + + /* Insert EEOP_*_FETCHSOME steps as needed */ + ExecInitExprSlots(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + /* * ExecInitExpr: prepare an expression tree for execution * @@ -122,32 +159,7 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, ExprState * ExecInitExpr(Expr *node, PlanState *parent) { - ExprState *state; - ExprEvalStep scratch = {0}; - - /* Special case: NULL expression produces a NULL ExprState pointer */ - if (node == NULL) - return NULL; - - /* Initialize ExprState with empty step list */ - state = makeNode(ExprState); - state->expr = node; - state->parent = parent; - state->ext_params = NULL; - - /* Insert EEOP_*_FETCHSOME steps as needed */ - ExecInitExprSlots(state, (Node *) node); - - /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); - - /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; - ExprEvalPushStep(state, &scratch); - - ExecReadyExpr(state); - - return state; + return ExecInitExprInternal(node, parent, NULL, NULL, NULL); } /* @@ -159,32 +171,20 @@ ExecInitExpr(Expr *node, PlanState *parent) ExprState * ExecInitExprWithParams(Expr *node, ParamListInfo ext_params) { - ExprState *state; - ExprEvalStep scratch = {0}; - - /* Special case: NULL expression produces a NULL ExprState pointer */ - if (node == NULL) - return NULL; - - /* Initialize ExprState with empty step list */ - state = makeNode(ExprState); - state->expr = node; - state->parent = NULL; - state->ext_params = ext_params; - - /* Insert EEOP_*_FETCHSOME steps as needed */ - ExecInitExprSlots(state, (Node *) node); - - /* Compile the expression proper */ - ExecInitExprRec(node, state, &state->resvalue, &state->resnull); - - /* Finally, append a DONE step */ - scratch.opcode = EEOP_DONE; - ExprEvalPushStep(state, &scratch); - - ExecReadyExpr(state); + return ExecInitExprInternal(node, NULL, ext_params, NULL, NULL); +} - return state; +/* + * ExecInitExprWithCaseValue: prepare an expression tree for execution + * + * This is the same as ExecInitExpr, except that a pointer to the value for + * CasTestExpr is passed here. + */ +ExprState * +ExecInitExprWithCaseValue(Expr *node, PlanState *parent, + Datum *caseval, bool *casenull) +{ + return ExecInitExprInternal(node, parent, NULL, caseval, casenull); } /* @@ -2428,6 +2428,253 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + ExecInitExprRec(jve->raw_expr, state, resv, resnull); + + if (jve->formatted_expr) + { + Datum *innermost_caseval = state->innermost_caseval; + bool *innermost_isnull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec(jve->formatted_expr, state, resv, resnull); + + state->innermost_caseval = innermost_caseval; + state->innermost_casenull = innermost_isnull; + } + break; + } + + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + List *args = ctor->args; + ListCell *lc; + int nargs = list_length(args); + int argno = 0; + + if (ctor->func) + { + ExecInitExprRec(ctor->func, state, resv, resnull); + } + else if ((ctor->type == JSCTOR_JSON_PARSE && !ctor->unique) || + ctor->type == JSCTOR_JSON_SERIALIZE) + { + /* Use the value of the first argument as a result */ + ExecInitExprRec(linitial(args), state, resv, resnull); + } + else + { + scratch.opcode = EEOP_JSON_CONSTRUCTOR; + scratch.d.json_constructor.constructor = ctor; + scratch.d.json_constructor.arg_values = palloc(sizeof(Datum) * nargs); + scratch.d.json_constructor.arg_nulls = palloc(sizeof(bool) * nargs); + scratch.d.json_constructor.arg_types = palloc(sizeof(Oid) * nargs); + scratch.d.json_constructor.nargs = nargs; + + foreach(lc, args) + { + Expr *arg = (Expr *) lfirst(lc); + + scratch.d.json_constructor.arg_types[argno] = exprType((Node *) arg); + + if (IsA(arg, Const)) + { + /* Don't evaluate const arguments every round */ + Const *con = (Const *) arg; + + scratch.d.json_constructor.arg_values[argno] = con->constvalue; + scratch.d.json_constructor.arg_nulls[argno] = con->constisnull; + } + else + { + ExecInitExprRec(arg, state, + &scratch.d.json_constructor.arg_values[argno], + &scratch.d.json_constructor.arg_nulls[argno]); + } + argno++; + } + + /* prepare type cache for datum_to_json[b]() */ + if (ctor->type == JSCTOR_JSON_SCALAR) + { + bool is_jsonb = + ctor->returning->format->format == JS_FORMAT_JSONB; + + scratch.d.json_constructor.arg_type_cache = + palloc(sizeof(*scratch.d.json_constructor.arg_type_cache) * nargs); + + for (int i = 0; i < nargs; i++) + { + int category; + Oid outfuncid; + Oid typid = scratch.d.json_constructor.arg_types[i]; + + if (is_jsonb) + { + JsonbTypeCategory jbcat; + + jsonb_categorize_type(typid, &jbcat, &outfuncid); + + category = (int) jbcat; + } + else + { + JsonTypeCategory jscat; + + json_categorize_type(typid, &jscat, &outfuncid); + + category = (int) jscat; + } + + scratch.d.json_constructor.arg_type_cache[i].outfuncid = outfuncid; + scratch.d.json_constructor.arg_type_cache[i].category = category; + } + } + + ExprEvalPushStep(state, &scratch); + } + + if (ctor->coercion) + { + Datum *innermost_caseval = state->innermost_caseval; + bool *innermost_isnull = state->innermost_casenull; + + state->innermost_caseval = resv; + state->innermost_casenull = resnull; + + ExecInitExprRec(ctor->coercion, state, resv, resnull); + + state->innermost_caseval = innermost_caseval; + state->innermost_casenull = innermost_isnull; + } + } + break; + + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + ExecInitExprRec((Expr *) pred->expr, state, resv, resnull); + + scratch.opcode = EEOP_IS_JSON; + scratch.d.is_json.pred = pred; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_JsonExpr: + { + JsonExpr *jexpr = castNode(JsonExpr, node); + ListCell *argexprlc; + ListCell *argnamelc; + + scratch.opcode = EEOP_JSONEXPR; + scratch.d.jsonexpr.jsexpr = jexpr; + + scratch.d.jsonexpr.formatted_expr = + palloc(sizeof(*scratch.d.jsonexpr.formatted_expr)); + + ExecInitExprRec((Expr *) jexpr->formatted_expr, state, + &scratch.d.jsonexpr.formatted_expr->value, + &scratch.d.jsonexpr.formatted_expr->isnull); + + scratch.d.jsonexpr.pathspec = + palloc(sizeof(*scratch.d.jsonexpr.pathspec)); + + ExecInitExprRec((Expr *) jexpr->path_spec, state, + &scratch.d.jsonexpr.pathspec->value, + &scratch.d.jsonexpr.pathspec->isnull); + + scratch.d.jsonexpr.res_expr = + palloc(sizeof(*scratch.d.jsonexpr.res_expr)); + + scratch.d.jsonexpr.result_expr = jexpr->result_coercion + ? ExecInitExprWithCaseValue((Expr *) jexpr->result_coercion->expr, + state->parent, + &scratch.d.jsonexpr.res_expr->value, + &scratch.d.jsonexpr.res_expr->isnull) + : NULL; + + scratch.d.jsonexpr.default_on_empty = !jexpr->on_empty ? NULL : + ExecInitExpr((Expr *) jexpr->on_empty->default_expr, + state->parent); + + scratch.d.jsonexpr.default_on_error = + ExecInitExpr((Expr *) jexpr->on_error->default_expr, + state->parent); + + if (jexpr->omit_quotes || + (jexpr->result_coercion && jexpr->result_coercion->via_io)) + { + Oid typinput; + + /* lookup the result type's input function */ + getTypeInputInfo(jexpr->returning->typid, &typinput, + &scratch.d.jsonexpr.input.typioparam); + fmgr_info(typinput, &scratch.d.jsonexpr.input.func); + } + + scratch.d.jsonexpr.args = NIL; + + forboth(argexprlc, jexpr->passing_values, + argnamelc, jexpr->passing_names) + { + Expr *argexpr = (Expr *) lfirst(argexprlc); + String *argname = lfirst_node(String, argnamelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->name = pstrdup(argname->val); + var->typid = exprType((Node *) argexpr); + var->typmod = exprTypmod((Node *) argexpr); + var->estate = ExecInitExpr(argexpr, state->parent); + var->econtext = NULL; + var->evaluated = false; + var->value = (Datum) 0; + var->isnull = true; + + scratch.d.jsonexpr.args = + lappend(scratch.d.jsonexpr.args, var); + } + + scratch.d.jsonexpr.cache = NULL; + + if (jexpr->coercions) + { + JsonCoercion **coercion; + struct JsonCoercionState *cstate; + Datum *caseval; + bool *casenull; + + scratch.d.jsonexpr.coercion_expr = + palloc(sizeof(*scratch.d.jsonexpr.coercion_expr)); + + caseval = &scratch.d.jsonexpr.coercion_expr->value; + casenull = &scratch.d.jsonexpr.coercion_expr->isnull; + + for (cstate = &scratch.d.jsonexpr.coercions.null, + coercion = &jexpr->coercions->null; + coercion <= &jexpr->coercions->composite; + coercion++, cstate++) + { + cstate->coercion = *coercion; + cstate->estate = *coercion ? + ExecInitExprWithCaseValue((Expr *)(*coercion)->expr, + state->parent, + caseval, casenull) : NULL; + } + } + + ExprEvalPushStep(state, &scratch); + break; + } + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index eb49817cee..0d4d30853e 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -57,22 +57,31 @@ #include "postgres.h" #include "access/heaptoast.h" +#include "access/xact.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/sequence.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" +#include "parser/parse_expr.h" #include "pgstat.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/date.h" #include "utils/datum.h" #include "utils/expandedrecord.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/jsonfuncs.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/resowner.h" #include "utils/timestamp.h" #include "utils/typcache.h" #include "utils/xml.h" @@ -477,6 +486,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_GROUPING_FUNC, &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, + &&CASE_EEOP_JSON_CONSTRUCTOR, + &&CASE_EEOP_IS_JSON, + &&CASE_EEOP_JSONEXPR, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1786,7 +1798,27 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { /* too complex for an inline implementation */ ExecEvalAggOrderedTransTuple(state, op, econtext); + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSON_CONSTRUCTOR) + { + /* too complex for an inline implementation */ + ExecEvalJsonConstructor(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_IS_JSON) + { + /* too complex for an inline implementation */ + ExecEvalJsonIsPredicate(state, op); + EEO_NEXT(); + } + + EEO_CASE(EEOP_JSONEXPR) + { + /* too complex for an inline implementation */ + ExecEvalJson(state, op, econtext); EEO_NEXT(); } @@ -3900,6 +3932,91 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) } } +void +ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op) +{ + JsonIsPredicate *pred = op->d.is_json.pred; + Datum js = *op->resvalue; + Oid exprtype; + bool res; + + if (*op->resnull) + { + *op->resvalue = BoolGetDatum(false); + return; + } + + exprtype = exprType(pred->expr); + + if (exprtype == TEXTOID || exprtype == JSONOID) + { + text *json = DatumGetTextP(js); + + if (pred->value_type == JS_TYPE_ANY) + res = true; + else + { + switch (json_get_first_token(json, false)) + { + case JSON_TOKEN_OBJECT_START: + res = pred->value_type == JS_TYPE_OBJECT; + break; + case JSON_TOKEN_ARRAY_START: + res = pred->value_type == JS_TYPE_ARRAY; + break; + case JSON_TOKEN_STRING: + case JSON_TOKEN_NUMBER: + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + case JSON_TOKEN_NULL: + res = pred->value_type == JS_TYPE_SCALAR; + break; + default: + res = false; + break; + } + } + + /* + * Do full parsing pass only for uniqueness check or for + * JSON text validation. + */ + if (res && (pred->unique_keys || exprtype == TEXTOID)) + res = json_validate(json, pred->unique_keys, false); + } + else if (exprtype == JSONBOID) + { + if (pred->value_type == JS_TYPE_ANY) + res = true; + else + { + Jsonb *jb = DatumGetJsonbP(js); + + switch (pred->value_type) + { + case JS_TYPE_OBJECT: + res = JB_ROOT_IS_OBJECT(jb); + break; + case JS_TYPE_ARRAY: + res = JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb); + break; + case JS_TYPE_SCALAR: + res = JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb); + break; + default: + res = false; + break; + } + } + + /* Key uniqueness check is redundant for jsonb */ + } + else + res = false; + + *op->resvalue = BoolGetDatum(res); +} + /* * ExecEvalGroupingFunc * @@ -4386,3 +4503,612 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans, MemoryContextSwitchTo(oldContext); } + +/* + * Evaluate a JSON constructor expression. + */ +void +ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + Datum res; + JsonConstructorExpr *ctor = op->d.json_constructor.constructor; + bool is_jsonb = ctor->returning->format->format == JS_FORMAT_JSONB; + bool isnull = false; + + if (ctor->type == JSCTOR_JSON_ARRAY) + res = (is_jsonb ? + jsonb_build_array_worker : + json_build_array_worker)(op->d.json_constructor.nargs, + op->d.json_constructor.arg_values, + op->d.json_constructor.arg_nulls, + op->d.json_constructor.arg_types, + op->d.json_constructor.constructor->absent_on_null); + else if (ctor->type == JSCTOR_JSON_OBJECT) + res = (is_jsonb ? + jsonb_build_object_worker : + json_build_object_worker)(op->d.json_constructor.nargs, + op->d.json_constructor.arg_values, + op->d.json_constructor.arg_nulls, + op->d.json_constructor.arg_types, + op->d.json_constructor.constructor->absent_on_null, + op->d.json_constructor.constructor->unique); + else if (ctor->type == JSCTOR_JSON_SCALAR) + { + if (op->d.json_constructor.arg_nulls[0]) + { + res = (Datum) 0; + isnull = true; + } + else + { + Datum value = op->d.json_constructor.arg_values[0]; + int category = op->d.json_constructor.arg_type_cache[0].category; + Oid outfuncid = op->d.json_constructor.arg_type_cache[0].outfuncid; + + if (is_jsonb) + res = to_jsonb_worker(value, category, outfuncid); + else + res = to_json_worker(value, category, outfuncid); + } + } + else if (ctor->type == JSCTOR_JSON_PARSE) + { + if (op->d.json_constructor.arg_nulls[0]) + { + res = (Datum) 0; + isnull = true; + } + else + { + Datum value = op->d.json_constructor.arg_values[0]; + text *js = DatumGetTextP(value); + + if (is_jsonb) + res = jsonb_from_text(js, true); + else + { + (void) json_validate(js, true, true); + res = value; + } + } + } + else + { + res = (Datum) 0; + elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type); + } + + *op->resvalue = res; + *op->resnull = isnull; +} + +/* + * Evaluate a JSON error/empty behavior result. + */ +static Datum +ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, + ExprState *default_estate, bool *is_null) +{ + *is_null = false; + + switch (behavior->btype) + { + case JSON_BEHAVIOR_EMPTY_ARRAY: + return JsonbPGetDatum(JsonbMakeEmptyArray()); + + case JSON_BEHAVIOR_EMPTY_OBJECT: + return JsonbPGetDatum(JsonbMakeEmptyObject()); + + case JSON_BEHAVIOR_TRUE: + return BoolGetDatum(true); + + case JSON_BEHAVIOR_FALSE: + return BoolGetDatum(false); + + case JSON_BEHAVIOR_NULL: + case JSON_BEHAVIOR_UNKNOWN: + *is_null = true; + return (Datum) 0; + + case JSON_BEHAVIOR_DEFAULT: + return ExecEvalExpr(default_estate, econtext, is_null); + + default: + elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype); + return (Datum) 0; + } +} + +/* + * Evaluate a coercion of a JSON item to the target type. + */ +static Datum +ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, + Datum res, bool *isNull, void *p, bool *error) +{ + ExprState *estate = p; + + if (estate) /* coerce using specified expression */ + return ExecEvalExpr(estate, econtext, isNull); + + if (op->d.jsonexpr.jsexpr->op != IS_JSON_EXISTS) + { + JsonCoercion *coercion = op->d.jsonexpr.jsexpr->result_coercion; + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res); + + if ((coercion && coercion->via_io) || + (jexpr->omit_quotes && !*isNull && + JB_ROOT_IS_SCALAR(jb))) + { + /* strip quotes and call typinput function */ + char *str = *isNull ? NULL : JsonbUnquote(jb); + + return InputFunctionCall(&op->d.jsonexpr.input.func, str, + op->d.jsonexpr.input.typioparam, + jexpr->returning->typmod); + } + else if (coercion && coercion->via_populate) + return json_populate_type(res, JSONBOID, + jexpr->returning->typid, + jexpr->returning->typmod, + &op->d.jsonexpr.cache, + econtext->ecxt_per_query_memory, + isNull); + } + + if (op->d.jsonexpr.result_expr) + { + op->d.jsonexpr.res_expr->value = res; + op->d.jsonexpr.res_expr->isnull = *isNull; + + res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull); + } + + return res; +} + +/* + * Evaluate a JSON path variable caching computed value. + */ +int +EvalJsonPathVar(void *cxt, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject) +{ + JsonPathVariableEvalContext *var = NULL; + List *vars = cxt; + ListCell *lc; + int id = 1; + + if (!varName) + return list_length(vars); + + foreach(lc, vars) + { + var = lfirst(lc); + + if (!strncmp(var->name, varName, varNameLen)) + break; + + var = NULL; + id++; + } + + if (!var) + return -1; + + if (!var->evaluated) + { + var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); + var->evaluated = true; + } + + if (var->isnull) + { + val->type = jbvNull; + return 0; + } + + JsonItemFromDatum(var->value, var->typid, var->typmod, val); + + *baseObject = *val; + return id; +} + +/* + * Prepare SQL/JSON item coercion to the output type. Returned a datum of the + * corresponding SQL type and a pointer to the coercion state. + */ +Datum +ExecPrepareJsonItemCoercion(JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pcoercion) +{ + struct JsonCoercionState *coercion; + Datum res; + JsonbValue buf; + + if (item->type == jbvBinary && + JsonContainerIsScalar(item->val.binary.data)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(item->val.binary.data, &buf); + item = &buf; + Assert(res); + } + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + coercion = &coercions->null; + res = (Datum) 0; + break; + + case jbvString: + coercion = &coercions->string; + res = PointerGetDatum( + cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); + break; + + case jbvNumeric: + coercion = &coercions->numeric; + res = NumericGetDatum(item->val.numeric); + break; + + case jbvBool: + coercion = &coercions->boolean; + res = BoolGetDatum(item->val.boolean); + break; + + case jbvDatetime: + res = item->val.datetime.value; + switch (item->val.datetime.typid) + { + case DATEOID: + coercion = &coercions->date; + break; + case TIMEOID: + coercion = &coercions->time; + break; + case TIMETZOID: + coercion = &coercions->timetz; + break; + case TIMESTAMPOID: + coercion = &coercions->timestamp; + break; + case TIMESTAMPTZOID: + coercion = &coercions->timestamptz; + break; + default: + elog(ERROR, "unexpected jsonb datetime type oid %d", + item->val.datetime.typid); + return (Datum) 0; + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + coercion = &coercions->composite; + res = JsonbPGetDatum(JsonbValueToJsonb(item)); + break; + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + return (Datum) 0; + } + + *pcoercion = coercion; + + return res; +} + +typedef Datum (*JsonFunc)(ExprEvalStep *op, ExprContext *econtext, + Datum item, bool *resnull, void *p, bool *error); + +static Datum +ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op, + ExprContext *econtext, + Datum res, bool *resnull, + void *p, bool *error, bool subtrans) +{ + MemoryContext oldcontext; + ResourceOwner oldowner; + + if (!subtrans) + /* No need to use subtransactions. */ + return func(op, econtext, res, resnull, p, error); + + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION + * and execute the corresponding ON ERROR behavior then. + */ + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + Assert(error); + + BeginInternalSubTransaction(NULL); + /* Want to execute expressions inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + + PG_TRY(); + { + res = func(op, econtext, res, resnull, p, error); + + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + } + PG_CATCH(); + { + ErrorData *edata; + + /* Save error info in oldcontext */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != + ERRCODE_DATA_EXCEPTION) + ReThrowError(edata); + + res = (Datum) 0; + *error = true; + } + PG_END_TRY(); + + return res; +} + + +typedef struct +{ + JsonPath *path; + bool *error; + bool coercionInSubtrans; +} ExecEvalJsonExprContext; + +static Datum +ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, + Datum item, bool *resnull, void *pcxt, + bool *error) +{ + ExecEvalJsonExprContext *cxt = pcxt; + JsonPath *path = cxt->path; + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + ExprState *estate = NULL; + bool empty = false; + Datum res = (Datum) 0; + + switch (jexpr->op) + { + case IS_JSON_QUERY: + res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error, + op->d.jsonexpr.args); + if (error && *error) + { + *resnull = true; + return (Datum) 0; + } + *resnull = !DatumGetPointer(res); + break; + + case IS_JSON_VALUE: + { + struct JsonCoercionState *jcstate; + JsonbValue *jbv = JsonPathValue(item, path, &empty, error, + op->d.jsonexpr.args); + + if (error && *error) + return (Datum) 0; + + if (!jbv) /* NULL or empty */ + break; + + Assert(!empty); + + *resnull = false; + + /* coerce scalar item to the output type */ + if (jexpr->returning->typid == JSONOID || + jexpr->returning->typid == JSONBOID) + { + /* Use result coercion from json[b] to the output type */ + res = JsonbPGetDatum(JsonbValueToJsonb(jbv)); + break; + } + + /* Use coercion from SQL/JSON item type to the output type */ + res = ExecPrepareJsonItemCoercion(jbv, + op->d.jsonexpr.jsexpr->returning, + &op->d.jsonexpr.coercions, + &jcstate); + + if (jcstate->coercion && + (jcstate->coercion->via_io || + jcstate->coercion->via_populate)) + { + if (error) + { + *error = true; + return (Datum) 0; + } + /* + * Coercion via I/O means here that the cast to the target + * type simply does not exist. + */ + ereport(ERROR, + /* + * XXX Standard says about a separate error code + * ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE + * but does not define its number. + */ + (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED), + errmsg("SQL/JSON item cannot be cast to target type"))); + } + else if (!jcstate->estate) + return res; /* no coercion */ + + /* coerce using specific expression */ + estate = jcstate->estate; + op->d.jsonexpr.coercion_expr->value = res; + op->d.jsonexpr.coercion_expr->isnull = *resnull; + break; + } + + case IS_JSON_EXISTS: + { + bool exists = JsonPathExists(item, path, + op->d.jsonexpr.args, + error); + + *resnull = error && *error; + res = BoolGetDatum(exists); + + if (!op->d.jsonexpr.result_expr) + return res; + + /* coerce using result expression */ + estate = op->d.jsonexpr.result_expr; + op->d.jsonexpr.res_expr->value = res; + op->d.jsonexpr.res_expr->isnull = *resnull; + break; + } + + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); + return (Datum) 0; + } + + if (empty) + { + Assert(jexpr->on_empty); /* it is not JSON_EXISTS */ + + if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR) + { + if (error) + { + *error = true; + return (Datum) 0; + } + + ereport(ERROR, + (errcode(ERRCODE_NO_SQL_JSON_ITEM), + errmsg("no SQL/JSON item"))); + } + + if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT) + /* + * Execute DEFAULT expression as a coercion expression, because + * its result is already coerced to the target type. + */ + estate = op->d.jsonexpr.default_on_empty; + else + /* Execute ON EMPTY behavior */ + res = ExecEvalJsonBehavior(econtext, jexpr->on_empty, + op->d.jsonexpr.default_on_empty, + resnull); + } + + return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext, + res, resnull, estate, error, + cxt->coercionInSubtrans); +} + +bool +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, + struct JsonCoercionsState *coercions) +{ + if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR) + return false; + + if (jsexpr->op == IS_JSON_EXISTS && !jsexpr->result_coercion) + return false; + + if (!coercions) + return true; + + return false; +} + +/* ---------------------------------------------------------------- + * ExecEvalJson + * ---------------------------------------------------------------- + */ +void +ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ExecEvalJsonExprContext cxt; + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + Datum item; + Datum res = (Datum) 0; + JsonPath *path; + ListCell *lc; + bool error = false; + bool needSubtrans; + bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR; + + *op->resnull = true; /* until we get a result */ + *op->resvalue = (Datum) 0; + + if (op->d.jsonexpr.formatted_expr->isnull || op->d.jsonexpr.pathspec->isnull) + { + /* execute domain checks for NULLs */ + (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, + NULL, NULL); + + Assert(*op->resnull); + return; + } + + item = op->d.jsonexpr.formatted_expr->value; + path = DatumGetJsonPathP(op->d.jsonexpr.pathspec->value); + + /* reset JSON path variable contexts */ + foreach(lc, op->d.jsonexpr.args) + { + JsonPathVariableEvalContext *var = lfirst(lc); + + var->econtext = econtext; + var->evaluated = false; + } + + needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &op->d.jsonexpr.coercions); + + cxt.path = path; + cxt.error = throwErrors ? NULL : &error; + cxt.coercionInSubtrans = !needSubtrans && !throwErrors; + Assert(!needSubtrans || cxt.error); + + res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item, + op->resnull, &cxt, cxt.error, + needSubtrans); + + if (error) + { + /* Execute ON ERROR behavior */ + res = ExecEvalJsonBehavior(econtext, jexpr->on_error, + op->d.jsonexpr.default_on_error, + op->resnull); + + /* result is already coerced in DEFAULT behavior case */ + if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT) + res = ExecEvalJsonExprCoercion(op, econtext, res, + op->resnull, + NULL, NULL); + } + + *op->resvalue = res; +} diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 6d1181225e..15c9ee44c0 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2348,6 +2348,24 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_JSON_CONSTRUCTOR: + build_EvalXFunc(b, mod, "ExecEvalJsonConstructor", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_IS_JSON: + build_EvalXFunc(b, mod, "ExecEvalJsonIsPredicate", + v_state, op); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + + case EEOP_JSONEXPR: + build_EvalXFunc(b, mod, "ExecEvalJson", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_LAST: Assert(false); break; diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 2deb65c5b5..b507d98ca1 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -131,6 +131,9 @@ void *referenced_functions[] = ExecEvalSysVar, ExecEvalWholeRowVar, ExecEvalXmlExpr, + ExecEvalJsonConstructor, + ExecEvalJsonIsPredicate, + ExecEvalJson, MakeExpandedObjectReadOnlyInternal, slot_getmissingattrs, slot_getsomeattrs_int, diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 228387eaee..ba2eb0f894 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2295,6 +2295,373 @@ _copyOnConflictExpr(const OnConflictExpr *from) return newnode; } + +/* + * _copyJsonFormat + */ +static JsonFormat * +_copyJsonFormat(const JsonFormat *from) +{ + JsonFormat *newnode = makeNode(JsonFormat); + + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(encoding); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonReturning + */ +static JsonReturning * +_copyJsonReturning(const JsonReturning *from) +{ + JsonReturning *newnode = makeNode(JsonReturning); + + COPY_NODE_FIELD(format); + COPY_SCALAR_FIELD(typid); + COPY_SCALAR_FIELD(typmod); + + return newnode; +} + +/* + * _copyJsonValueExpr + */ +static JsonValueExpr * +_copyJsonValueExpr(const JsonValueExpr *from) +{ + JsonValueExpr *newnode = makeNode(JsonValueExpr); + + COPY_NODE_FIELD(raw_expr); + COPY_NODE_FIELD(formatted_expr); + COPY_NODE_FIELD(format); + + return newnode; +} + +/* + * _copyJsonScalarExpr + */ +static JsonScalarExpr * +_copyJsonScalarExpr(const JsonScalarExpr *from) +{ + JsonScalarExpr *newnode = makeNode(JsonScalarExpr); + + COPY_NODE_FIELD(expr); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonSerializeExpr + */ +static JsonSerializeExpr * +_copyJsonSerializeExpr(const JsonSerializeExpr *from) +{ + JsonSerializeExpr *newnode = makeNode(JsonSerializeExpr); + + COPY_NODE_FIELD(expr); + COPY_NODE_FIELD(output); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonConstructorExpr + */ +static JsonConstructorExpr * +_copyJsonConstructorExpr(const JsonConstructorExpr *from) +{ + JsonConstructorExpr *newnode = makeNode(JsonConstructorExpr); + + COPY_SCALAR_FIELD(type); + COPY_NODE_FIELD(args); + COPY_NODE_FIELD(func); + COPY_NODE_FIELD(coercion); + COPY_NODE_FIELD(returning); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonKeyValue + */ +static JsonKeyValue * +_copyJsonKeyValue(const JsonKeyValue *from) +{ + JsonKeyValue *newnode = makeNode(JsonKeyValue); + + COPY_NODE_FIELD(key); + COPY_NODE_FIELD(value); + + return newnode; +} + +/* + * _copyJsonObjectConstructor + */ +static JsonObjectConstructor * +_copyJsonObjectConstructor(const JsonObjectConstructor *from) +{ + JsonObjectConstructor *newnode = makeNode(JsonObjectConstructor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonAggConstructor + */ +static JsonAggConstructor * +_copyJsonAggConstructor(const JsonAggConstructor *from) +{ + JsonAggConstructor *newnode = makeNode(JsonAggConstructor); + + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(agg_filter); + COPY_NODE_FIELD(agg_order); + COPY_NODE_FIELD(over); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonObjectAgg + */ +static JsonObjectAgg * +_copyJsonObjectAgg(const JsonObjectAgg *from) +{ + JsonObjectAgg *newnode = makeNode(JsonObjectAgg); + + COPY_NODE_FIELD(constructor); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + + return newnode; +} + +/* + * _copyJsonOutput + */ +static JsonOutput * +_copyJsonOutput(const JsonOutput *from) +{ + JsonOutput *newnode = makeNode(JsonOutput); + + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(returning); + + return newnode; +} + +/* + * _copyJsonArrayConstructor + */ +static JsonArrayConstructor * +_copyJsonArrayConstructor(const JsonArrayConstructor *from) +{ + JsonArrayConstructor *newnode = makeNode(JsonArrayConstructor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonArrayAgg + */ +static JsonArrayAgg * +_copyJsonArrayAgg(const JsonArrayAgg *from) +{ + JsonArrayAgg *newnode = makeNode(JsonArrayAgg); + + COPY_NODE_FIELD(constructor); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + +/* + * _copyJsonArrayQueryConstructor + */ +static JsonArrayQueryConstructor * +_copyJsonArrayQueryConstructor(const JsonArrayQueryConstructor *from) +{ + JsonArrayQueryConstructor *newnode = makeNode(JsonArrayQueryConstructor); + + COPY_NODE_FIELD(query); + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(format); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonExpr + */ +static JsonExpr * +_copyJsonExpr(const JsonExpr *from) +{ + JsonExpr *newnode = makeNode(JsonExpr); + + COPY_SCALAR_FIELD(op); + COPY_NODE_FIELD(formatted_expr); + COPY_NODE_FIELD(result_coercion); + COPY_NODE_FIELD(format); + COPY_NODE_FIELD(path_spec); + COPY_NODE_FIELD(passing_values); + COPY_NODE_FIELD(passing_names); + COPY_NODE_FIELD(returning); + COPY_NODE_FIELD(on_error); + COPY_NODE_FIELD(on_empty); + COPY_NODE_FIELD(coercions); + COPY_SCALAR_FIELD(wrapper); + COPY_SCALAR_FIELD(omit_quotes); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonCoercion + */ +static JsonCoercion * +_copyJsonCoercion(const JsonCoercion *from) +{ + JsonCoercion *newnode = makeNode(JsonCoercion); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(via_populate); + COPY_SCALAR_FIELD(via_io); + COPY_SCALAR_FIELD(collation); + + return newnode; +} + +/* + * _copyJsonItemCoercions + */ +static JsonItemCoercions * +_copyJsonItemCoercions(const JsonItemCoercions *from) +{ + JsonItemCoercions *newnode = makeNode(JsonItemCoercions); + + COPY_NODE_FIELD(null); + COPY_NODE_FIELD(string); + COPY_NODE_FIELD(numeric); + COPY_NODE_FIELD(boolean); + COPY_NODE_FIELD(date); + COPY_NODE_FIELD(time); + COPY_NODE_FIELD(timetz); + COPY_NODE_FIELD(timestamp); + COPY_NODE_FIELD(timestamptz); + COPY_NODE_FIELD(composite); + + return newnode; +} + +/* + * _copyJsonFuncExpr + */ +static JsonFuncExpr * +_copyJsonFuncExpr(const JsonFuncExpr *from) +{ + JsonFuncExpr *newnode = makeNode(JsonFuncExpr); + + COPY_SCALAR_FIELD(op); + COPY_NODE_FIELD(common); + COPY_NODE_FIELD(output); + COPY_NODE_FIELD(on_empty); + COPY_NODE_FIELD(on_error); + COPY_SCALAR_FIELD(wrapper); + COPY_SCALAR_FIELD(omit_quotes); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonIsPredicate + */ +static JsonIsPredicate * +_copyJsonIsPredicate(const JsonIsPredicate *from) +{ + JsonIsPredicate *newnode = makeNode(JsonIsPredicate); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(value_type); + COPY_SCALAR_FIELD(unique_keys); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonBehavior + */ +static JsonBehavior * +_copyJsonBehavior(const JsonBehavior *from) +{ + JsonBehavior *newnode = makeNode(JsonBehavior); + + COPY_SCALAR_FIELD(btype); + COPY_NODE_FIELD(default_expr); + + return newnode; +} + +/* + * _copyJsonCommon + */ +static JsonCommon * +_copyJsonCommon(const JsonCommon *from) +{ + JsonCommon *newnode = makeNode(JsonCommon); + + COPY_NODE_FIELD(expr); + COPY_NODE_FIELD(pathspec); + COPY_STRING_FIELD(pathname); + COPY_NODE_FIELD(passing); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonArgument + */ +static JsonArgument * +_copyJsonArgument(const JsonArgument *from) +{ + JsonArgument *newnode = makeNode(JsonArgument); + + COPY_NODE_FIELD(val); + COPY_STRING_FIELD(name); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5306,6 +5673,72 @@ copyObjectImpl(const void *from) case T_OnConflictExpr: retval = _copyOnConflictExpr(from); break; + case T_JsonFormat: + retval = _copyJsonFormat(from); + break; + case T_JsonReturning: + retval = _copyJsonReturning(from); + break; + case T_JsonValueExpr: + retval = _copyJsonValueExpr(from); + break; + case T_JsonScalarExpr: + retval = _copyJsonScalarExpr(from); + break; + case T_JsonSerializeExpr: + retval = _copyJsonSerializeExpr(from); + break; + case T_JsonKeyValue: + retval = _copyJsonKeyValue(from); + break; + case T_JsonConstructorExpr: + retval = _copyJsonConstructorExpr(from); + break; + case T_JsonObjectConstructor: + retval = _copyJsonObjectConstructor(from); + break; + case T_JsonAggConstructor: + retval = _copyJsonAggConstructor(from); + break; + case T_JsonObjectAgg: + retval = _copyJsonObjectAgg(from); + break; + case T_JsonOutput: + retval = _copyJsonOutput(from); + break; + case T_JsonArrayConstructor: + retval = _copyJsonArrayConstructor(from); + break; + case T_JsonArrayQueryConstructor: + retval = _copyJsonArrayQueryConstructor(from); + break; + case T_JsonArrayAgg: + retval = _copyJsonArrayAgg(from); + break; + case T_JsonIsPredicate: + retval = _copyJsonIsPredicate(from); + break; + case T_JsonFuncExpr: + retval = _copyJsonFuncExpr(from); + break; + case T_JsonExpr: + retval = _copyJsonExpr(from); + break; + case T_JsonCommon: + retval = _copyJsonCommon(from); + break; + case T_JsonBehavior: + retval = _copyJsonBehavior(from); + break; + case T_JsonArgument: + retval = _copyJsonArgument(from); + break; + case T_JsonCoercion: + retval = _copyJsonCoercion(from); + break; + case T_JsonItemCoercions: + retval = _copyJsonItemCoercions(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 800f588b5c..ffb186825f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -841,6 +841,242 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b) return true; } +static bool +_equalJsonFormat(const JsonFormat *a, const JsonFormat *b) +{ + COMPARE_SCALAR_FIELD(format); + COMPARE_SCALAR_FIELD(encoding); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonReturning(const JsonReturning *a, const JsonReturning *b) +{ + COMPARE_NODE_FIELD(format); + COMPARE_SCALAR_FIELD(typid); + COMPARE_SCALAR_FIELD(typmod); + + return true; +} + +static bool +_equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b) +{ + COMPARE_NODE_FIELD(raw_expr); + COMPARE_NODE_FIELD(formatted_expr); + COMPARE_NODE_FIELD(format); + + return true; +} + +static bool +_equalJsonScalarExpr(const JsonScalarExpr *a, const JsonScalarExpr *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonSerializeExpr(const JsonSerializeExpr *a, const JsonSerializeExpr *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_NODE_FIELD(output); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonConstructorExpr(const JsonConstructorExpr *a, const JsonConstructorExpr *b) +{ + COMPARE_SCALAR_FIELD(type); + COMPARE_NODE_FIELD(args); + COMPARE_NODE_FIELD(func); + COMPARE_NODE_FIELD(coercion); + COMPARE_NODE_FIELD(returning); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonKeyValue(const JsonKeyValue *a, const JsonKeyValue *b) +{ + COMPARE_NODE_FIELD(key); + COMPARE_NODE_FIELD(value); + + return true; +} + +static bool +_equalJsonObjectConstructor(const JsonObjectConstructor *a, + const JsonObjectConstructor *b) +{ + COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(output); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonAggConstructor(const JsonAggConstructor *a, + const JsonAggConstructor *b) +{ + COMPARE_NODE_FIELD(output); + COMPARE_NODE_FIELD(agg_filter); + COMPARE_NODE_FIELD(agg_order); + COMPARE_NODE_FIELD(over); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonObjectAgg(const JsonObjectAgg *a, const JsonObjectAgg *b) +{ + COMPARE_NODE_FIELD(constructor); + COMPARE_NODE_FIELD(arg); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + + return true; +} + +static bool +_equalJsonOutput(const JsonOutput *a, const JsonOutput *b) +{ + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(returning); + + return true; +} + +static bool +_equalJsonArrayConstructor(const JsonArrayConstructor *a, + const JsonArrayConstructor *b) +{ + COMPARE_NODE_FIELD(exprs); + COMPARE_NODE_FIELD(output); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonArrayAgg(const JsonArrayAgg *a, const JsonArrayAgg *b) +{ + COMPARE_NODE_FIELD(constructor); + COMPARE_NODE_FIELD(arg); + COMPARE_SCALAR_FIELD(absent_on_null); + + return true; +} + +static bool +_equalJsonArrayQueryConstructor(const JsonArrayQueryConstructor *a, + const JsonArrayQueryConstructor *b) +{ + COMPARE_NODE_FIELD(query); + COMPARE_NODE_FIELD(output); + COMPARE_NODE_FIELD(format); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool +_equalJsonIsPredicate(const JsonIsPredicate *a, + const JsonIsPredicate *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(value_type); + COMPARE_SCALAR_FIELD(unique_keys); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +/* + * _equalJsonBehavior + */ +static bool +_equalJsonBehavior(const JsonBehavior *a, const JsonBehavior *b) +{ + COMPARE_SCALAR_FIELD(btype); + COMPARE_NODE_FIELD(default_expr); + + return true; +} + +/* + * _equalJsonExpr + */ +static bool +_equalJsonExpr(const JsonExpr *a, const JsonExpr *b) +{ + COMPARE_SCALAR_FIELD(op); + COMPARE_NODE_FIELD(formatted_expr); + COMPARE_NODE_FIELD(result_coercion); + COMPARE_NODE_FIELD(format); + COMPARE_NODE_FIELD(path_spec); + COMPARE_NODE_FIELD(passing_values); + COMPARE_NODE_FIELD(passing_names); + COMPARE_NODE_FIELD(returning); + COMPARE_NODE_FIELD(on_error); + COMPARE_NODE_FIELD(on_empty); + COMPARE_NODE_FIELD(coercions); + COMPARE_SCALAR_FIELD(wrapper); + COMPARE_SCALAR_FIELD(omit_quotes); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +/* + * _equalJsonCoercion + */ +static bool +_equalJsonCoercion(const JsonCoercion *a, const JsonCoercion *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(via_populate); + COMPARE_SCALAR_FIELD(via_io); + COMPARE_SCALAR_FIELD(collation); + + return true; +} + +/* + * _equalJsonItemCoercions + */ +static bool +_equalJsonItemCoercions(const JsonItemCoercions *a, const JsonItemCoercions *b) +{ + COMPARE_NODE_FIELD(null); + COMPARE_NODE_FIELD(string); + COMPARE_NODE_FIELD(numeric); + COMPARE_NODE_FIELD(boolean); + COMPARE_NODE_FIELD(date); + COMPARE_NODE_FIELD(time); + COMPARE_NODE_FIELD(timetz); + COMPARE_NODE_FIELD(timestamp); + COMPARE_NODE_FIELD(timestamptz); + COMPARE_NODE_FIELD(composite); + + return true; +} + /* * Stuff from pathnodes.h */ @@ -3326,6 +3562,39 @@ equal(const void *a, const void *b) case T_JoinExpr: retval = _equalJoinExpr(a, b); break; + case T_JsonFormat: + retval = _equalJsonFormat(a, b); + break; + case T_JsonReturning: + retval = _equalJsonReturning(a, b); + break; + case T_JsonValueExpr: + retval = _equalJsonValueExpr(a, b); + break; + case T_JsonScalarExpr: + retval = _equalJsonScalarExpr(a, b); + break; + case T_JsonSerializeExpr: + retval = _equalJsonSerializeExpr(a, b); + break; + case T_JsonConstructorExpr: + retval = _equalJsonConstructorExpr(a, b); + break; + case T_JsonIsPredicate: + retval = _equalJsonIsPredicate(a, b); + break; + case T_JsonBehavior: + retval = _equalJsonBehavior(a, b); + break; + case T_JsonExpr: + retval = _equalJsonExpr(a, b); + break; + case T_JsonCoercion: + retval = _equalJsonCoercion(a, b); + break; + case T_JsonItemCoercions: + retval = _equalJsonItemCoercions(a, b); + break; /* * RELATION NODES @@ -3897,6 +4166,30 @@ equal(const void *a, const void *b) case T_PublicationTable: retval = _equalPublicationTable(a, b); break; + case T_JsonKeyValue: + retval = _equalJsonKeyValue(a, b); + break; + case T_JsonObjectConstructor: + retval = _equalJsonObjectConstructor(a, b); + break; + case T_JsonAggConstructor: + retval = _equalJsonAggConstructor(a, b); + break; + case T_JsonObjectAgg: + retval = _equalJsonObjectAgg(a, b); + break; + case T_JsonOutput: + retval = _equalJsonOutput(a, b); + break; + case T_JsonArrayConstructor: + retval = _equalJsonArrayConstructor(a, b); + break; + case T_JsonArrayQueryConstructor: + retval = _equalJsonArrayQueryConstructor(a, b); + break; + case T_JsonArrayAgg: + retval = _equalJsonArrayAgg(a, b); + break; default: elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 01c110cd2f..ca374dcb29 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -19,6 +19,7 @@ #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "utils/errcodes.h" #include "utils/lsyscache.h" @@ -815,3 +816,105 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols) v->va_cols = va_cols; return v; } + +/* + * makeJsonFormat - + * creates a JsonFormat node + */ +JsonFormat * +makeJsonFormat(JsonFormatType type, JsonEncoding encoding, int location) +{ + JsonFormat *jf = makeNode(JsonFormat); + + jf->format = type; + jf->encoding = encoding; + jf->location = location; + + return jf; +} + +/* + * makeJsonValueExpr - + * creates a JsonValueExpr node + */ +JsonValueExpr * +makeJsonValueExpr(Expr *expr, JsonFormat *format) +{ + JsonValueExpr *jve = makeNode(JsonValueExpr); + + jve->raw_expr = expr; + jve->formatted_expr = NULL; + jve->format = format; + + return jve; +} + +/* + * makeJsonBehavior - + * creates a JsonBehavior node + */ +JsonBehavior * +makeJsonBehavior(JsonBehaviorType type, Node *default_expr) +{ + JsonBehavior *behavior = makeNode(JsonBehavior); + + behavior->btype = type; + behavior->default_expr = default_expr; + + return behavior; +} + +/* + * makeJsonEncoding - + * converts JSON encoding name to enum JsonEncoding + */ +JsonEncoding +makeJsonEncoding(char *name) +{ + if (!pg_strcasecmp(name, "utf8")) + return JS_ENC_UTF8; + if (!pg_strcasecmp(name, "utf16")) + return JS_ENC_UTF16; + if (!pg_strcasecmp(name, "utf32")) + return JS_ENC_UTF32; + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized JSON encoding: %s", name))); + + return JS_ENC_DEFAULT; +} + +/* + * makeJsonKeyValue - + * creates a JsonKeyValue node + */ +Node * +makeJsonKeyValue(Node *key, Node *value) +{ + JsonKeyValue *n = makeNode(JsonKeyValue); + + n->key = (Expr *) key; + n->value = castNode(JsonValueExpr, value); + + return (Node *) n; +} + +/* + * makeJsonIsPredicate - + * creates a JsonIsPredicate node + */ +Node * +makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType value_type, + bool unique_keys, int location) +{ + JsonIsPredicate *n = makeNode(JsonIsPredicate); + + n->expr = expr; + n->format = format; + n->value_type = value_type; + n->unique_keys = unique_keys; + n->location = location; + + return (Node *) n; +} diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index e276264882..97bd0bac6b 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -250,6 +250,25 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + { + const JsonValueExpr *jve = (const JsonValueExpr *) expr; + + type = exprType((Node *) (jve->formatted_expr ? jve->formatted_expr : jve->raw_expr)); + } + break; + case T_JsonConstructorExpr: + type = ((const JsonConstructorExpr *) expr)->returning->typid; + break; + case T_JsonIsPredicate: + type = BOOLOID; + break; + case T_JsonExpr: + type = ((const JsonExpr *) expr)->returning->typid; + break; + case T_JsonCoercion: + type = exprType(((const JsonCoercion *) expr)->expr); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -482,6 +501,14 @@ exprTypmod(const Node *expr) return ((const SetToDefault *) expr)->typeMod; case T_PlaceHolderVar: return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); + case T_JsonValueExpr: + return exprTypmod((Node *) ((const JsonValueExpr *) expr)->formatted_expr); + case T_JsonConstructorExpr: + return ((const JsonConstructorExpr *) expr)->returning->typmod; + case T_JsonExpr: + return ((JsonExpr *) expr)->returning->typmod; + case T_JsonCoercion: + return exprTypmod(((const JsonCoercion *) expr)->expr); default: break; } @@ -956,6 +983,37 @@ exprCollation(const Node *expr) case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->formatted_expr); + break; + case T_JsonConstructorExpr: + { + const JsonConstructorExpr *ctor = (const JsonConstructorExpr *) expr; + + if (ctor->coercion) + coll = exprCollation((Node *) ctor->coercion); + else + coll = InvalidOid; + } + break; + case T_JsonIsPredicate: + coll = InvalidOid; /* result is always an boolean type */ + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) expr; + JsonCoercion *coercion = jexpr->result_coercion; + + if (!coercion) + coll = InvalidOid; + else if (coercion->expr) + coll = exprCollation(coercion->expr); + else if (coercion->via_io || coercion->via_populate) + coll = coercion->collation; + else + coll = InvalidOid; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1168,6 +1226,38 @@ exprSetCollation(Node *expr, Oid collation) /* NextValueExpr's result is an integer type ... */ Assert(!OidIsValid(collation)); /* ... so never set a collation */ break; + case T_JsonValueExpr: + exprSetCollation((Node *) ((JsonValueExpr *) expr)->formatted_expr, + collation); + break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) expr; + + if (ctor->coercion) + exprSetCollation((Node *) ctor->coercion, collation); + else + Assert(!OidIsValid(collation)); /* result is always a json[b] type */ + } + break; + case T_JsonIsPredicate: + Assert(!OidIsValid(collation)); /* result is always boolean */ + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) expr; + JsonCoercion *coercion = jexpr->result_coercion; + + if (!coercion) + Assert(!OidIsValid(collation)); + else if (coercion->expr) + exprSetCollation(coercion->expr, collation); + else if (coercion->via_io || coercion->via_populate) + coercion->collation = collation; + else + Assert(!OidIsValid(collation)); + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1614,6 +1704,24 @@ exprLocation(const Node *expr) case T_PartitionRangeDatum: loc = ((const PartitionRangeDatum *) expr)->location; break; + case T_JsonValueExpr: + loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->raw_expr); + break; + case T_JsonConstructorExpr: + loc = ((const JsonConstructorExpr *) expr)->location; + break; + case T_JsonIsPredicate: + loc = ((const JsonIsPredicate *) expr)->location; + break; + case T_JsonExpr: + { + const JsonExpr *jsexpr = (const JsonExpr *) expr; + + /* consider both function name and leftmost arg */ + loc = leftmostLoc(jsexpr->location, + exprLocation(jsexpr->formatted_expr)); + } + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2328,6 +2436,78 @@ expression_tree_walker(Node *node, return true; } break; + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + if (walker(jve->raw_expr, context)) + return true; + if (walker(jve->formatted_expr, context)) + return true; + } + break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + if (walker(ctor->args, context)) + return true; + if (walker(ctor->func, context)) + return true; + if (walker(ctor->coercion, context)) + return true; + } + break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + if (walker(jexpr->formatted_expr, context)) + return true; + if (walker(jexpr->result_coercion, context)) + return true; + if (walker(jexpr->passing_values, context)) + return true; + /* we assume walker doesn't care about passing_names */ + if (jexpr->on_empty && + walker(jexpr->on_empty->default_expr, context)) + return true; + if (walker(jexpr->on_error->default_expr, context)) + return true; + if (walker(jexpr->coercions, context)) + return true; + } + break; + case T_JsonCoercion: + return walker(((JsonCoercion *) node)->expr, context); + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = (JsonItemCoercions *) node; + + if (walker(coercions->null, context)) + return true; + if (walker(coercions->string, context)) + return true; + if (walker(coercions->numeric, context)) + return true; + if (walker(coercions->boolean, context)) + return true; + if (walker(coercions->date, context)) + return true; + if (walker(coercions->time, context)) + return true; + if (walker(coercions->timetz, context)) + return true; + if (walker(coercions->timestamp, context)) + return true; + if (walker(coercions->timestamptz, context)) + return true; + if (walker(coercions->composite, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -2658,6 +2838,7 @@ expression_tree_mutator(Node *node, case T_RangeTblRef: case T_SortGroupClause: case T_CTESearchClause: + case T_JsonFormat: return (Node *) copyObject(node); case T_WithCheckOption: { @@ -3267,6 +3448,101 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_JsonReturning: + { + JsonReturning *jr = (JsonReturning *) node; + JsonReturning *newnode; + + FLATCOPY(newnode, jr, JsonReturning); + MUTATE(newnode->format, jr->format, JsonFormat *); + + return (Node *) newnode; + } + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + JsonValueExpr *newnode; + + FLATCOPY(newnode, jve, JsonValueExpr); + MUTATE(newnode->raw_expr, jve->raw_expr, Expr *); + MUTATE(newnode->formatted_expr, jve->formatted_expr, Expr *); + MUTATE(newnode->format, jve->format, JsonFormat *); + + return (Node *) newnode; + } + case T_JsonConstructorExpr: + { + JsonConstructorExpr *jve = (JsonConstructorExpr *) node; + JsonConstructorExpr *newnode; + + FLATCOPY(newnode, jve, JsonConstructorExpr); + MUTATE(newnode->args, jve->args, List *); + MUTATE(newnode->func, jve->func, Expr *); + MUTATE(newnode->coercion, jve->coercion, Expr *); + MUTATE(newnode->returning, jve->returning, JsonReturning *); + + return (Node *) newnode; + } + break; + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + JsonIsPredicate *newnode; + + FLATCOPY(newnode, pred, JsonIsPredicate); + MUTATE(newnode->expr, pred->expr, Node *); + + return (Node *) newnode; + } + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + JsonExpr *newnode; + + FLATCOPY(newnode, jexpr, JsonExpr); + MUTATE(newnode->path_spec, jexpr->path_spec, Node *); + MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *); + MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *); + MUTATE(newnode->passing_values, jexpr->passing_values, List *); + /* assume mutator does not care about passing_names */ + if (newnode->on_empty) + MUTATE(newnode->on_empty->default_expr, + jexpr->on_empty->default_expr, Node *); + MUTATE(newnode->on_error->default_expr, + jexpr->on_error->default_expr, Node *); + return (Node *) newnode; + } + break; + case T_JsonCoercion: + { + JsonCoercion *coercion = (JsonCoercion *) node; + JsonCoercion *newnode; + + FLATCOPY(newnode, coercion, JsonCoercion); + MUTATE(newnode->expr, coercion->expr, Node *); + return (Node *) newnode; + } + break; + case T_JsonItemCoercions: + { + JsonItemCoercions *coercions = (JsonItemCoercions *) node; + JsonItemCoercions *newnode; + + FLATCOPY(newnode, coercions, JsonItemCoercions); + MUTATE(newnode->null, coercions->null, JsonCoercion *); + MUTATE(newnode->string, coercions->string, JsonCoercion *); + MUTATE(newnode->numeric, coercions->numeric, JsonCoercion *); + MUTATE(newnode->boolean, coercions->boolean, JsonCoercion *); + MUTATE(newnode->date, coercions->date, JsonCoercion *); + MUTATE(newnode->time, coercions->time, JsonCoercion *); + MUTATE(newnode->timetz, coercions->timetz, JsonCoercion *); + MUTATE(newnode->timestamp, coercions->timestamp, JsonCoercion *); + MUTATE(newnode->timestamptz, coercions->timestamptz, JsonCoercion *); + MUTATE(newnode->composite, coercions->composite, JsonCoercion *); + return (Node *) newnode; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3974,6 +4250,157 @@ raw_expression_tree_walker(Node *node, case T_CommonTableExpr: /* search_clause and cycle_clause are not interesting here */ return walker(((CommonTableExpr *) node)->ctequery, context); + case T_JsonReturning: + return walker(((JsonReturning *) node)->format, context); + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + if (walker(jve->raw_expr, context)) + return true; + if (walker(jve->formatted_expr, context)) + return true; + if (walker(jve->format, context)) + return true; + } + break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + if (walker(ctor->args, context)) + return true; + if (walker(ctor->func, context)) + return true; + if (walker(ctor->coercion, context)) + return true; + if (walker(ctor->returning, context)) + return true; + } + break; + case T_JsonOutput: + { + JsonOutput *out = (JsonOutput *) node; + + if (walker(out->typeName, context)) + return true; + if (walker(out->returning, context)) + return true; + } + break; + case T_JsonKeyValue: + { + JsonKeyValue *jkv = (JsonKeyValue *) node; + + if (walker(jkv->key, context)) + return true; + if (walker(jkv->value, context)) + return true; + } + break; + case T_JsonObjectConstructor: + { + JsonObjectConstructor *joc = (JsonObjectConstructor *) node; + + if (walker(joc->output, context)) + return true; + if (walker(joc->exprs, context)) + return true; + } + break; + case T_JsonArrayConstructor: + { + JsonArrayConstructor *jac = (JsonArrayConstructor *) node; + + if (walker(jac->output, context)) + return true; + if (walker(jac->exprs, context)) + return true; + } + break; + case T_JsonAggConstructor: + { + JsonAggConstructor *ctor = (JsonAggConstructor *) node; + + if (walker(ctor->output, context)) + return true; + if (walker(ctor->agg_order, context)) + return true; + if (walker(ctor->agg_filter, context)) + return true; + if (walker(ctor->over, context)) + return true; + } + break; + case T_JsonObjectAgg: + { + JsonObjectAgg *joa = (JsonObjectAgg *) node; + + if (walker(joa->constructor, context)) + return true; + if (walker(joa->arg, context)) + return true; + } + break; + case T_JsonArrayAgg: + { + JsonArrayAgg *jaa = (JsonArrayAgg *) node; + + if (walker(jaa->constructor, context)) + return true; + if (walker(jaa->arg, context)) + return true; + } + break; + case T_JsonArrayQueryConstructor: + { + JsonArrayQueryConstructor *jaqc = (JsonArrayQueryConstructor *) node; + + if (walker(jaqc->output, context)) + return true; + if (walker(jaqc->query, context)) + return true; + } + break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); + case T_JsonArgument: + return walker(((JsonArgument *) node)->val, context); + case T_JsonCommon: + { + JsonCommon *jc = (JsonCommon *) node; + + if (walker(jc->expr, context)) + return true; + if (walker(jc->pathspec, context)) + return true; + if (walker(jc->passing, context)) + return true; + } + break; + case T_JsonBehavior: + { + JsonBehavior *jb = (JsonBehavior *) node; + + if (jb->btype == JSON_BEHAVIOR_DEFAULT && + walker(jb->default_expr, context)) + return true; + } + break; + case T_JsonFuncExpr: + { + JsonFuncExpr *jfe = (JsonFuncExpr *) node; + + if (walker(jfe->common, context)) + return true; + if (jfe->output && walker(jfe->output, context)) + return true; + if (walker(jfe->on_empty, context)) + return true; + if (walker(jfe->on_error, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index f2a6a6e7a0..c3aa51c639 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1748,6 +1748,120 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node) WRITE_NODE_FIELD(exclRelTlist); } +static void +_outJsonFormat(StringInfo str, const JsonFormat *node) +{ + WRITE_NODE_TYPE("JSONFORMAT"); + + WRITE_ENUM_FIELD(format, JsonFormatType); + WRITE_ENUM_FIELD(encoding, JsonEncoding); + WRITE_LOCATION_FIELD(location); +} + +static void +_outJsonReturning(StringInfo str, const JsonReturning *node) +{ + WRITE_NODE_TYPE("JSONRETURNING"); + + WRITE_NODE_FIELD(format); + WRITE_OID_FIELD(typid); + WRITE_INT_FIELD(typmod); +} + +static void +_outJsonValueExpr(StringInfo str, const JsonValueExpr *node) +{ + WRITE_NODE_TYPE("JSONVALUEEXPR"); + + WRITE_NODE_FIELD(raw_expr); + WRITE_NODE_FIELD(formatted_expr); + WRITE_NODE_FIELD(format); +} + +static void +_outJsonConstructorExpr(StringInfo str, const JsonConstructorExpr *node) +{ + WRITE_NODE_TYPE("JSONCTOREXPR"); + + WRITE_NODE_FIELD(args); + WRITE_NODE_FIELD(func); + WRITE_NODE_FIELD(coercion); + WRITE_INT_FIELD(type); + WRITE_NODE_FIELD(returning); + WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(absent_on_null); + WRITE_LOCATION_FIELD(location); +} + +static void +_outJsonIsPredicate(StringInfo str, const JsonIsPredicate *node) +{ + WRITE_NODE_TYPE("JSONISPREDICATE"); + + WRITE_NODE_FIELD(expr); + WRITE_ENUM_FIELD(value_type, JsonValueType); + WRITE_BOOL_FIELD(unique_keys); + WRITE_LOCATION_FIELD(location); +} + +static void +_outJsonBehavior(StringInfo str, const JsonBehavior *node) +{ + WRITE_NODE_TYPE("JSONBEHAVIOR"); + + WRITE_ENUM_FIELD(btype, JsonBehaviorType); + WRITE_NODE_FIELD(default_expr); +} + +static void +_outJsonExpr(StringInfo str, const JsonExpr *node) +{ + WRITE_NODE_TYPE("JSONEXPR"); + + WRITE_ENUM_FIELD(op, JsonExprOp); + WRITE_NODE_FIELD(formatted_expr); + WRITE_NODE_FIELD(result_coercion); + WRITE_NODE_FIELD(format); + WRITE_NODE_FIELD(path_spec); + WRITE_NODE_FIELD(passing_values); + WRITE_NODE_FIELD(passing_names); + WRITE_NODE_FIELD(returning); + WRITE_NODE_FIELD(on_error); + WRITE_NODE_FIELD(on_empty); + WRITE_NODE_FIELD(coercions); + WRITE_ENUM_FIELD(wrapper, JsonWrapper); + WRITE_BOOL_FIELD(omit_quotes); + WRITE_LOCATION_FIELD(location); +} + +static void +_outJsonCoercion(StringInfo str, const JsonCoercion *node) +{ + WRITE_NODE_TYPE("JSONCOERCION"); + + WRITE_NODE_FIELD(expr); + WRITE_BOOL_FIELD(via_populate); + WRITE_BOOL_FIELD(via_io); + WRITE_OID_FIELD(collation); +} + +static void +_outJsonItemCoercions(StringInfo str, const JsonItemCoercions *node) +{ + WRITE_NODE_TYPE("JSONITEMCOERCIONS"); + + WRITE_NODE_FIELD(null); + WRITE_NODE_FIELD(string); + WRITE_NODE_FIELD(numeric); + WRITE_NODE_FIELD(boolean); + WRITE_NODE_FIELD(date); + WRITE_NODE_FIELD(time); + WRITE_NODE_FIELD(timetz); + WRITE_NODE_FIELD(timestamp); + WRITE_NODE_FIELD(timestamptz); + WRITE_NODE_FIELD(composite); +} + /***************************************************************************** * * Stuff from pathnodes.h. @@ -4521,6 +4635,33 @@ outNode(StringInfo str, const void *obj) case T_PartitionRangeDatum: _outPartitionRangeDatum(str, obj); break; + case T_JsonFormat: + _outJsonFormat(str, obj); + break; + case T_JsonReturning: + _outJsonReturning(str, obj); + break; + case T_JsonValueExpr: + _outJsonValueExpr(str, obj); + break; + case T_JsonConstructorExpr: + _outJsonConstructorExpr(str, obj); + break; + case T_JsonIsPredicate: + _outJsonIsPredicate(str, obj); + break; + case T_JsonBehavior: + _outJsonBehavior(str, obj); + break; + case T_JsonExpr: + _outJsonExpr(str, obj); + break; + case T_JsonCoercion: + _outJsonCoercion(str, obj); + break; + case T_JsonItemCoercions: + _outJsonItemCoercions(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 0dd1ad7dfc..cbffe4695e 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1391,6 +1391,165 @@ _readOnConflictExpr(void) READ_DONE(); } +/* + * _readJsonFormat + */ +static JsonFormat * +_readJsonFormat(void) +{ + READ_LOCALS(JsonFormat); + + READ_ENUM_FIELD(format, JsonFormatType); + READ_ENUM_FIELD(encoding, JsonEncoding); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* + * _readJsonReturning + */ +static JsonReturning * +_readJsonReturning(void) +{ + READ_LOCALS(JsonReturning); + + READ_NODE_FIELD(format); + READ_OID_FIELD(typid); + READ_INT_FIELD(typmod); + + READ_DONE(); +} + +/* + * _readJsonValueExpr + */ +static JsonValueExpr * +_readJsonValueExpr(void) +{ + READ_LOCALS(JsonValueExpr); + + READ_NODE_FIELD(raw_expr); + READ_NODE_FIELD(formatted_expr); + READ_NODE_FIELD(format); + + READ_DONE(); +} + +/* + * _readJsonConstructorExpr + */ +static JsonConstructorExpr * +_readJsonConstructorExpr(void) +{ + READ_LOCALS(JsonConstructorExpr); + + READ_NODE_FIELD(args); + READ_NODE_FIELD(func); + READ_NODE_FIELD(coercion); + READ_INT_FIELD(type); + READ_NODE_FIELD(returning); + READ_BOOL_FIELD(unique); + READ_BOOL_FIELD(absent_on_null); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* + * _readJsonBehavior + */ +static JsonBehavior * +_readJsonBehavior(void) +{ + READ_LOCALS(JsonBehavior); + + READ_ENUM_FIELD(btype, JsonBehaviorType); + READ_NODE_FIELD(default_expr); + + READ_DONE(); +} + +/* + * _readJsonExpr + */ +static JsonExpr * +_readJsonExpr(void) +{ + READ_LOCALS(JsonExpr); + + READ_ENUM_FIELD(op, JsonExprOp); + READ_NODE_FIELD(formatted_expr); + READ_NODE_FIELD(result_coercion); + READ_NODE_FIELD(format); + READ_NODE_FIELD(path_spec); + READ_NODE_FIELD(passing_values); + READ_NODE_FIELD(passing_names); + READ_NODE_FIELD(returning); + READ_NODE_FIELD(on_error); + READ_NODE_FIELD(on_empty); + READ_NODE_FIELD(coercions); + READ_ENUM_FIELD(wrapper, JsonWrapper); + READ_BOOL_FIELD(omit_quotes); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* + * _readJsonCoercion + */ +static JsonCoercion * +_readJsonCoercion(void) +{ + READ_LOCALS(JsonCoercion); + + READ_NODE_FIELD(expr); + READ_BOOL_FIELD(via_populate); + READ_BOOL_FIELD(via_io); + READ_OID_FIELD(collation); + + READ_DONE(); +} + +/* + * _readJsonItemCoercions + */ +static JsonItemCoercions * +_readJsonItemCoercions(void) +{ + READ_LOCALS(JsonItemCoercions); + + READ_NODE_FIELD(null); + READ_NODE_FIELD(string); + READ_NODE_FIELD(numeric); + READ_NODE_FIELD(boolean); + READ_NODE_FIELD(date); + READ_NODE_FIELD(time); + READ_NODE_FIELD(timetz); + READ_NODE_FIELD(timestamp); + READ_NODE_FIELD(timestamptz); + READ_NODE_FIELD(composite); + + READ_DONE(); +} + +/* + * _readJsonIsPredicate + */ +static JsonIsPredicate * +_readJsonIsPredicate() +{ + READ_LOCALS(JsonIsPredicate); + + READ_NODE_FIELD(expr); + READ_ENUM_FIELD(value_type, JsonValueType); + READ_BOOL_FIELD(unique_keys); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + /* * Stuff from pathnodes.h. * @@ -2973,6 +3132,24 @@ parseNodeString(void) return_value = _readPartitionBoundSpec(); else if (MATCH("PARTITIONRANGEDATUM", 19)) return_value = _readPartitionRangeDatum(); + else if (MATCH("JSONFORMAT", 10)) + return_value = _readJsonFormat(); + else if (MATCH("JSONRETURNING", 13)) + return_value = _readJsonReturning(); + else if (MATCH("JSONVALUEEXPR", 13)) + return_value = _readJsonValueExpr(); + else if (MATCH("JSONCTOREXPR", 12)) + return_value = _readJsonConstructorExpr(); + else if (MATCH("JSONISPREDICATE", 15)) + return_value = _readJsonIsPredicate(); + else if (MATCH("JSONBEHAVIOR", 12)) + return_value = _readJsonBehavior(); + else if (MATCH("JSONEXPR", 8)) + return_value = _readJsonExpr(); + else if (MATCH("JSONCOERCION", 12)) + return_value = _readJsonCoercion(); + else if (MATCH("JSONITEMCOERCIONS", 17)) + return_value = _readJsonItemCoercions(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 1fd53b40bb..a412014e12 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -4540,7 +4540,8 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) IsA(node, SQLValueFunction) || IsA(node, XmlExpr) || IsA(node, CoerceToDomain) || - IsA(node, NextValueExpr)) + IsA(node, NextValueExpr) || + IsA(node, JsonExpr)) { /* Treat all these as having cost 1 */ context->total.per_tuple += cpu_operator_cost; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 3412d31117..c1951c1caf 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -28,6 +28,7 @@ #include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/functions.h" +#include "executor/execExpr.h" #include "funcapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -50,6 +51,9 @@ #include "utils/builtins.h" #include "utils/datum.h" #include "utils/fmgroids.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -381,6 +385,45 @@ contain_mutable_functions_walker(Node *node, void *context) context)) return true; + if (IsA(node, JsonConstructorExpr)) + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + ListCell *lc; + bool is_jsonb = + ctor->returning->format->format == JS_FORMAT_JSONB; + + /* Check argument_type => json[b] conversions */ + foreach(lc, ctor->args) + { + Oid typid = exprType(lfirst(lc)); + + if (is_jsonb ? + !to_jsonb_is_immutable(typid) : + !to_json_is_immutable(typid)) + return true; + } + + /* Check all subnodes */ + } + + if (IsA(node, JsonExpr)) + { + JsonExpr *jexpr = castNode(JsonExpr, node); + Const *cnst; + + if (!IsA(jexpr->path_spec, Const)) + return true; + + cnst = castNode(Const, jexpr->path_spec); + + Assert(cnst->consttype == JSONPATHOID); + if (cnst->constisnull) + return false; + + return jspIsMutable(DatumGetJsonPathP(cnst->constvalue), + jexpr->passing_names, jexpr->passing_values); + } + if (IsA(node, SQLValueFunction)) { /* all variants of SQLValueFunction are stable */ @@ -852,6 +895,18 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) context, 0); } + /* JsonExpr is parallel-unsafe if subtransactions can be used. */ + else if (IsA(node, JsonExpr)) + { + JsonExpr *jsexpr = (JsonExpr *) node; + + if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL)) + { + context->max_hazard = PROPARALLEL_UNSAFE; + return true; + } + } + /* Recurse to check arguments */ return expression_tree_walker(node, max_parallel_hazard_walker, @@ -3511,6 +3566,29 @@ eval_const_expressions_mutator(Node *node, return ece_evaluate_expr((Node *) newcre); return (Node *) newcre; } + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + Node *raw = eval_const_expressions_mutator((Node *) jve->raw_expr, + context); + + if (raw && IsA(raw, Const)) + { + Node *formatted; + Node *save_case_val = context->case_val; + + context->case_val = raw; + + formatted = eval_const_expressions_mutator((Node *) jve->formatted_expr, + context); + + context->case_val = save_case_val; + + if (formatted && IsA(formatted, Const)) + return formatted; + } + break; + } default: break; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e3068a374e..b70949b122 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -62,6 +62,7 @@ #include "storage/lmgr.h" #include "utils/date.h" #include "utils/datetime.h" +#include "utils/jsonb.h" #include "utils/numeric.h" #include "utils/xml.h" @@ -259,6 +260,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); struct SelectLimit *selectlimit; SetQuantifier setquantifier; struct GroupClause *groupclause; + JsonBehavior *jsbehavior; + struct + { + JsonBehavior *on_empty; + JsonBehavior *on_error; + } on_behavior; + JsonQuotes js_quotes; } %type stmt toplevel_stmt schema_stmt routine_body_stmt @@ -534,7 +542,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type copy_options %type Typename SimpleTypename ConstTypename - GenericType Numeric opt_float + GenericType Numeric opt_float JsonType Character ConstCharacter CharacterWithLength CharacterWithoutLength ConstDatetime ConstInterval @@ -610,6 +618,73 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type hash_partbound_elem +%type json_format_clause_opt + json_representation + json_value_expr + json_func_expr + json_value_func_expr + json_query_expr + json_exists_predicate + json_parse_expr + json_scalar_expr + json_serialize_expr + json_api_common_syntax + json_context_item + json_argument + json_output_clause_opt + json_returning_clause_opt + json_value_constructor + json_object_constructor + json_object_constructor_args + json_object_constructor_args_opt + json_object_args + json_object_func_args + json_array_constructor + json_name_and_value + json_aggregate_func + json_object_aggregate_constructor + json_array_aggregate_constructor + json_path_specification + +%type json_name_and_value_list + json_value_expr_list + json_array_aggregate_order_by_clause_opt + json_arguments + json_passing_clause_opt + +%type json_table_path_name + json_as_path_name_clause_opt + +%type json_encoding + json_encoding_clause_opt + json_wrapper_clause_opt + json_wrapper_behavior + json_conditional_or_unconditional_opt + json_predicate_type_constraint_opt + +%type json_behavior_error + json_behavior_null + json_behavior_true + json_behavior_false + json_behavior_unknown + json_behavior_empty_array + json_behavior_empty_object + json_behavior_default + json_value_behavior + json_query_behavior + json_exists_error_behavior + json_exists_error_clause_opt + +%type json_value_on_behavior_clause_opt + json_query_on_behavior_clause_opt + +%type json_quotes_behavior + json_quotes_clause_opt + +%type json_key_uniqueness_constraint_opt + json_object_constructor_null_clause_opt + json_array_constructor_null_clause_opt + /* * Non-keyword token types. These are hard-wired into the "flex" lexer. * They must be listed first so that their numeric codes do not depend on @@ -635,7 +710,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ /* ordinary key words in alphabetical order */ -%token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER +%token ABORT_P ABSENT ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ASENSITIVE ASSERTION ASSIGNMENT ASYMMETRIC ATOMIC AT ATTACH ATTRIBUTE AUTHORIZATION @@ -645,7 +720,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE CLUSTER COALESCE COLLATE COLLATION COLUMN COLUMNS COMMENT COMMENTS COMMIT - COMMITTED COMPRESSION CONCURRENTLY CONFIGURATION CONFLICT + COMMITTED COMPRESSION CONCURRENTLY CONDITIONAL CONFIGURATION CONFLICT CONNECTION CONSTRAINT CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CROSS CSV CUBE CURRENT_P CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA @@ -656,12 +731,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP - EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT - EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION + EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE + EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER FINALIZE FIRST_P FLOAT_P FOLLOWING FOR - FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS + FORCE FOREIGN FORMAT FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS GENERATED GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING GROUPS @@ -672,9 +747,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION - JOIN + JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG + JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_VALUE - KEY + KEY KEYS KEEP LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -687,7 +763,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC - OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR + OBJECT_P OF OFF OFFSET OIDS OLD OMIT ON ONLY OPERATOR OPTION OPTIONS OR ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER @@ -695,17 +771,17 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION - QUOTE + QUOTE QUOTES RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES - SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW - SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P - START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P + SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF + SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -713,7 +789,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P - UESCAPE UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN + UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING @@ -738,7 +814,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * as NOT, at least with respect to their left-hand subexpression. * NULLS_LA and WITH_LA are needed to make the grammar LALR(1). */ -%token NOT_LA NULLS_LA WITH_LA +%token NOT_LA NULLS_LA WITH_LA WITH_LA_UNIQUE WITHOUT_LA /* * The grammar likewise thinks these tokens are keywords, but they are never @@ -756,6 +832,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ +%right FORMAT %left UNION EXCEPT %left INTERSECT %left OR @@ -791,11 +868,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * Using the same precedence as IDENT seems right for the reasons given above. */ %nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ +%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */ +%nonassoc FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN UNIQUE JSON %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' +%left KEYS /* UNIQUE [ KEYS ] */ +%left OBJECT_P SCALAR VALUE_P /* JSON [ OBJECT | SCALAR | VALUE ] */ /* Unary Operators */ %left AT /* sets precedence for AT TIME ZONE */ %left COLLATE @@ -813,6 +894,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %left JOIN CROSS LEFT FULL RIGHT INNER_P NATURAL +%nonassoc empty_json_unique +%left WITHOUT WITH_LA_UNIQUE + %% /* @@ -12850,6 +12934,7 @@ SimpleTypename: $$->typmods = list_make2(makeIntConst(INTERVAL_FULL_RANGE, -1), makeIntConst($3, @3)); } + | JsonType { $$ = $1; } ; /* We have a separate ConstTypename to allow defaulting fixed-length @@ -12868,6 +12953,7 @@ ConstTypename: | ConstBit { $$ = $1; } | ConstCharacter { $$ = $1; } | ConstDatetime { $$ = $1; } + | JsonType { $$ = $1; } ; /* @@ -13161,7 +13247,7 @@ ConstInterval: opt_timezone: WITH_LA TIME ZONE { $$ = true; } - | WITHOUT TIME ZONE { $$ = false; } + | WITHOUT_LA TIME ZONE { $$ = false; } | /*EMPTY*/ { $$ = false; } ; @@ -13236,6 +13322,14 @@ interval_second: } ; +/* Mapping of PG jsonb types to SQL/JSON JSON type */ +JsonType: + JSON + { + $$ = SystemTypeName(SQLJSON_TYPE_NAME()); + $$->location = @1; + } + ; /***************************************************************************** * @@ -13692,6 +13786,46 @@ a_expr: c_expr { $$ = $1; } @2), @2); } + | a_expr + IS json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + $$ = makeJsonIsPredicate($1, format, $3, $4, @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeJsonIsPredicate($1, $3, $5, $6, @1); + } + */ + | a_expr + IS NOT + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + $$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS NOT + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1); + } + */ | DEFAULT { /* @@ -13774,6 +13908,25 @@ b_expr: c_expr } ; +json_predicate_type_constraint_opt: + JSON { $$ = JS_TYPE_ANY; } + | JSON VALUE_P { $$ = JS_TYPE_ANY; } + | JSON ARRAY { $$ = JS_TYPE_ARRAY; } + | JSON OBJECT_P { $$ = JS_TYPE_OBJECT; } + | JSON SCALAR { $$ = JS_TYPE_SCALAR; } + ; + +json_key_uniqueness_constraint_opt: + WITH_LA_UNIQUE unique_keys { $$ = true; } + | WITHOUT unique_keys { $$ = false; } + | /* EMPTY */ %prec empty_json_unique { $$ = false; } + ; + +unique_keys: + UNIQUE + | UNIQUE KEYS + ; + /* * Productions that can be used in both a_expr and b_expr. * @@ -14026,6 +14179,15 @@ func_expr: func_application within_group_clause filter_clause over_clause n->over = $4; $$ = (Node *) n; } + | json_aggregate_func filter_clause over_clause + { + JsonAggConstructor *n = IsA($1, JsonObjectAgg) ? + ((JsonObjectAgg *) $1)->constructor : + ((JsonArrayAgg *) $1)->constructor; + n->agg_filter = $2; + n->over = $3; + $$ = (Node *) $1; + } | func_expr_common_subexpr { $$ = $1; } ; @@ -14039,6 +14201,7 @@ func_expr: func_application within_group_clause filter_clause over_clause func_expr_windowless: func_application { $$ = $1; } | func_expr_common_subexpr { $$ = $1; } + | json_aggregate_func { $$ = $1; } ; /* @@ -14326,6 +14489,8 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | json_func_expr + { $$ = $1; } ; /* @@ -15024,6 +15189,530 @@ opt_asymmetric: ASYMMETRIC | /*EMPTY*/ ; +/* SQL/JSON support */ +json_func_expr: + json_value_constructor + | json_value_func_expr + | json_query_expr + | json_exists_predicate + | json_parse_expr + | json_scalar_expr + | json_serialize_expr + ; + +json_parse_expr: + JSON '(' json_value_expr json_key_uniqueness_constraint_opt + json_returning_clause_opt ')' + { + JsonParseExpr *n = makeNode(JsonParseExpr); + n->expr = (JsonValueExpr *) $3; + n->unique_keys = $4; + n->output = (JsonOutput *) $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_scalar_expr: + JSON_SCALAR '(' a_expr json_returning_clause_opt ')' + { + JsonScalarExpr *n = makeNode(JsonScalarExpr); + n->expr = (Expr *) $3; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_serialize_expr: + JSON_SERIALIZE '(' json_value_expr json_output_clause_opt ')' + { + JsonSerializeExpr *n = makeNode(JsonSerializeExpr); + n->expr = (JsonValueExpr *) $3; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_value_func_expr: + JSON_VALUE '(' + json_api_common_syntax + json_returning_clause_opt + json_value_on_behavior_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + n->op = IS_JSON_VALUE; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->on_empty = $5.on_empty; + n->on_error = $5.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_api_common_syntax: + json_context_item ',' json_path_specification + json_as_path_name_clause_opt + json_passing_clause_opt + { + JsonCommon *n = makeNode(JsonCommon); + n->expr = (JsonValueExpr *) $1; + n->pathspec = $3; + n->pathname = $4; + n->passing = $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_context_item: + json_value_expr { $$ = $1; } + ; + +json_path_specification: + a_expr { $$ = $1; } + ; + +json_as_path_name_clause_opt: + AS json_table_path_name { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_path_name: + name { $$ = $1; } + ; + +json_passing_clause_opt: + PASSING json_arguments { $$ = $2; } + | /* EMPTY */ { $$ = NIL; } + ; + +json_arguments: + json_argument { $$ = list_make1($1); } + | json_arguments ',' json_argument { $$ = lappend($1, $3); } + ; + +json_argument: + json_value_expr AS ColLabel + { + JsonArgument *n = makeNode(JsonArgument); + n->val = (JsonValueExpr *) $1; + n->name = $3; + $$ = (Node *) n; + } + ; + +json_value_expr: + a_expr json_format_clause_opt + { + $$ = (Node *) makeJsonValueExpr((Expr *) $1, castNode(JsonFormat, $2)); + } + ; + +json_format_clause_opt: + FORMAT json_representation + { + $$ = $2; + castNode(JsonFormat, $$)->location = @1; + } + | /* EMPTY */ + { + $$ = (Node *) makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + } + ; + +json_representation: + JSON json_encoding_clause_opt + { + $$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, $2, @1); + } + /* | other implementation defined JSON representation options (BSON, AVRO etc) */ + ; + +json_encoding_clause_opt: + ENCODING json_encoding { $$ = $2; } + | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } + ; + +json_encoding: + name { $$ = makeJsonEncoding($1); } + ; + +json_behavior_error: + ERROR_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); } + ; + +json_behavior_null: + NULL_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_NULL, NULL); } + ; + +json_behavior_true: + TRUE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_TRUE, NULL); } + ; + +json_behavior_false: + FALSE_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_FALSE, NULL); } + ; + +json_behavior_unknown: + UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); } + ; + +json_behavior_empty_array: + EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } + /* non-standard, for Oracle compatibility only */ + | EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } + ; + +json_behavior_empty_object: + EMPTY_P OBJECT_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); } + ; + +json_behavior_default: + DEFAULT a_expr { $$ = makeJsonBehavior(JSON_BEHAVIOR_DEFAULT, $2); } + ; + + +json_value_behavior: + json_behavior_null + | json_behavior_error + | json_behavior_default + ; + +json_value_on_behavior_clause_opt: + json_value_behavior ON EMPTY_P + { $$.on_empty = $1; $$.on_error = NULL; } + | json_value_behavior ON EMPTY_P json_value_behavior ON ERROR_P + { $$.on_empty = $1; $$.on_error = $4; } + | json_value_behavior ON ERROR_P + { $$.on_empty = NULL; $$.on_error = $1; } + | /* EMPTY */ + { $$.on_empty = NULL; $$.on_error = NULL; } + ; + +json_query_expr: + JSON_QUERY '(' + json_api_common_syntax + json_output_clause_opt + json_wrapper_clause_opt + json_quotes_clause_opt + json_query_on_behavior_clause_opt + ')' + { + JsonFuncExpr *n = makeNode(JsonFuncExpr); + n->op = IS_JSON_QUERY; + n->common = (JsonCommon *) $3; + n->output = (JsonOutput *) $4; + n->wrapper = $5; + if (n->wrapper != JSW_NONE && $6 != JS_QUOTES_UNSPEC) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used"), + parser_errposition(@6))); + n->omit_quotes = $6 == JS_QUOTES_OMIT; + n->on_empty = $7.on_empty; + n->on_error = $7.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_wrapper_clause_opt: + json_wrapper_behavior WRAPPER { $$ = $1; } + | /* EMPTY */ { $$ = 0; } + ; + +json_wrapper_behavior: + WITHOUT array_opt { $$ = JSW_NONE; } + | WITH json_conditional_or_unconditional_opt array_opt { $$ = $2; } + ; + +array_opt: + ARRAY { } + | /* EMPTY */ { } + ; + +json_conditional_or_unconditional_opt: + CONDITIONAL { $$ = JSW_CONDITIONAL; } + | UNCONDITIONAL { $$ = JSW_UNCONDITIONAL; } + | /* EMPTY */ { $$ = JSW_UNCONDITIONAL; } + ; + +json_quotes_clause_opt: + json_quotes_behavior QUOTES json_on_scalar_string_opt { $$ = $1; } + | /* EMPTY */ { $$ = JS_QUOTES_UNSPEC; } + ; + +json_quotes_behavior: + KEEP { $$ = JS_QUOTES_KEEP; } + | OMIT { $$ = JS_QUOTES_OMIT; } + ; + +json_on_scalar_string_opt: + ON SCALAR STRING { } + | /* EMPTY */ { } + ; + +json_query_behavior: + json_behavior_error + | json_behavior_null + | json_behavior_empty_array + | json_behavior_empty_object + | json_behavior_default + ; + +json_query_on_behavior_clause_opt: + json_query_behavior ON EMPTY_P + { $$.on_empty = $1; $$.on_error = NULL; } + | json_query_behavior ON EMPTY_P json_query_behavior ON ERROR_P + { $$.on_empty = $1; $$.on_error = $4; } + | json_query_behavior ON ERROR_P + { $$.on_empty = NULL; $$.on_error = $1; } + | /* EMPTY */ + { $$.on_empty = NULL; $$.on_error = NULL; } + ; + +json_returning_clause_opt: + RETURNING Typename + { + JsonOutput *n = makeNode(JsonOutput); + n->typeName = $2; + n->returning = makeNode(JsonReturning); + n->returning->format = + makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, @2); + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; + +json_output_clause_opt: + RETURNING Typename json_format_clause_opt + { + JsonOutput *n = makeNode(JsonOutput); + n->typeName = $2; + n->returning = makeNode(JsonReturning); + n->returning->format = (JsonFormat *) $3; + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; + +json_exists_predicate: + JSON_EXISTS '(' + json_api_common_syntax + json_returning_clause_opt + json_exists_error_clause_opt + ')' + { + JsonFuncExpr *p = makeNode(JsonFuncExpr); + p->op = IS_JSON_EXISTS; + p->common = (JsonCommon *) $3; + p->output = (JsonOutput *) $4; + p->on_error = $5; + p->location = @1; + $$ = (Node *) p; + } + ; + +json_exists_error_clause_opt: + json_exists_error_behavior ON ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_exists_error_behavior: + json_behavior_error + | json_behavior_true + | json_behavior_false + | json_behavior_unknown + ; + +json_value_constructor: + json_object_constructor + | json_array_constructor + ; + +json_object_constructor: + JSON_OBJECT '(' json_object_args ')' + { + $$ = $3; + } + ; + +json_object_args: + json_object_constructor_args + | json_object_func_args + ; + +json_object_func_args: + func_arg_list + { + List *func = list_make1(makeString("json_object")); + $$ = (Node *) makeFuncCall(func, $1, COERCE_EXPLICIT_CALL, @1); + } + ; + +json_object_constructor_args: + json_object_constructor_args_opt json_output_clause_opt + { + JsonObjectConstructor *n = (JsonObjectConstructor *) $1; + n->output = (JsonOutput *) $2; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_object_constructor_args_opt: + json_name_and_value_list + json_object_constructor_null_clause_opt + json_key_uniqueness_constraint_opt + { + JsonObjectConstructor *n = makeNode(JsonObjectConstructor); + n->exprs = $1; + n->absent_on_null = $2; + n->unique = $3; + $$ = (Node *) n; + } + | /* EMPTY */ + { + JsonObjectConstructor *n = makeNode(JsonObjectConstructor); + n->exprs = NULL; + n->absent_on_null = false; + n->unique = false; + $$ = (Node *) n; + } + ; + +json_name_and_value_list: + json_name_and_value + { $$ = list_make1($1); } + | json_name_and_value_list ',' json_name_and_value + { $$ = lappend($1, $3); } + ; + +json_name_and_value: +/* TODO This is not supported due to conflicts + KEY c_expr VALUE_P json_value_expr %prec POSTFIXOP + { $$ = makeJsonKeyValue($2, $4); } + | +*/ + c_expr VALUE_P json_value_expr + { $$ = makeJsonKeyValue($1, $3); } + | + a_expr ':' json_value_expr + { $$ = makeJsonKeyValue($1, $3); } + ; + +json_object_constructor_null_clause_opt: + NULL_P ON NULL_P { $$ = false; } + | ABSENT ON NULL_P { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + +json_array_constructor: + JSON_ARRAY '(' + json_value_expr_list + json_array_constructor_null_clause_opt + json_output_clause_opt + ')' + { + JsonArrayConstructor *n = makeNode(JsonArrayConstructor); + n->exprs = $3; + n->absent_on_null = $4; + n->output = (JsonOutput *) $5; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + select_no_parens + /* json_format_clause_opt */ + /* json_array_constructor_null_clause_opt */ + json_output_clause_opt + ')' + { + JsonArrayQueryConstructor *n = makeNode(JsonArrayQueryConstructor); + n->query = $3; + n->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + /* n->format = $4; */ + n->absent_on_null = true /* $5 */; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + json_output_clause_opt + ')' + { + JsonArrayConstructor *n = makeNode(JsonArrayConstructor); + n->exprs = NIL; + n->absent_on_null = true; + n->output = (JsonOutput *) $3; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_value_expr_list: + json_value_expr { $$ = list_make1($1); } + | json_value_expr_list ',' json_value_expr { $$ = lappend($1, $3);} + ; + +json_array_constructor_null_clause_opt: + NULL_P ON NULL_P { $$ = false; } + | ABSENT ON NULL_P { $$ = true; } + | /* EMPTY */ { $$ = true; } + ; + +json_aggregate_func: + json_object_aggregate_constructor + | json_array_aggregate_constructor + ; + +json_object_aggregate_constructor: + JSON_OBJECTAGG '(' + json_name_and_value + json_object_constructor_null_clause_opt + json_key_uniqueness_constraint_opt + json_output_clause_opt + ')' + { + JsonObjectAgg *n = makeNode(JsonObjectAgg); + n->arg = (JsonKeyValue *) $3; + n->absent_on_null = $4; + n->unique = $5; + n->constructor = makeNode(JsonAggConstructor); + n->constructor->output = (JsonOutput *) $6; + n->constructor->agg_order = NULL; + n->constructor->location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_constructor: + JSON_ARRAYAGG '(' + json_value_expr + json_array_aggregate_order_by_clause_opt + json_array_constructor_null_clause_opt + json_output_clause_opt + ')' + { + JsonArrayAgg *n = makeNode(JsonArrayAgg); + n->arg = (JsonValueExpr *) $3; + n->absent_on_null = $5; + n->constructor = makeNode(JsonAggConstructor); + n->constructor->agg_order = $4; + n->constructor->output = (JsonOutput *) $6; + n->constructor->location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_order_by_clause_opt: + ORDER BY sortby_list { $$ = $3; } + | /* EMPTY */ { $$ = NIL; } + ; /***************************************************************************** * @@ -15490,6 +16179,7 @@ BareColLabel: IDENT { $$ = $1; } */ unreserved_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -15530,6 +16220,7 @@ unreserved_keyword: | COMMIT | COMMITTED | COMPRESSION + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -15566,10 +16257,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -15586,6 +16279,7 @@ unreserved_keyword: | FIRST_P | FOLLOWING | FORCE + | FORMAT | FORWARD | FUNCTION | FUNCTIONS @@ -15617,7 +16311,9 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | KEEP | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -15660,6 +16356,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -15689,6 +16386,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -15718,6 +16416,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -15769,6 +16468,7 @@ unreserved_keyword: | UESCAPE | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -15826,6 +16526,16 @@ col_name_keyword: | INT_P | INTEGER | INTERVAL + | JSON + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_EXISTS + | JSON_OBJECT + | JSON_OBJECTAGG + | JSON_QUERY + | JSON_SCALAR + | JSON_SERIALIZE + | JSON_VALUE | LEAST | NATIONAL | NCHAR @@ -15893,6 +16603,7 @@ type_func_name_keyword: | OVERLAPS | RIGHT | SIMILAR + | STRING | TABLESAMPLE | VERBOSE ; @@ -15994,6 +16705,7 @@ reserved_keyword: */ bare_label_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -16056,6 +16768,7 @@ bare_label_keyword: | COMMITTED | COMPRESSION | CONCURRENTLY + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -16108,11 +16821,13 @@ bare_label_keyword: | DROP | EACH | ELSE + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | END_P | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -16133,6 +16848,7 @@ bare_label_keyword: | FOLLOWING | FORCE | FOREIGN + | FORMAT | FORWARD | FREEZE | FULL @@ -16177,7 +16893,19 @@ bare_label_keyword: | IS | ISOLATION | JOIN + | JSON + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_EXISTS + | JSON_OBJECT + | JSON_OBJECTAGG + | JSON_QUERY + | JSON_SCALAR + | JSON_SERIALIZE + | JSON_VALUE + | KEEP | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -16234,6 +16962,7 @@ bare_label_keyword: | OFF | OIDS | OLD + | OMIT | ONLY | OPERATOR | OPTION @@ -16270,6 +16999,7 @@ bare_label_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REAL @@ -16303,6 +17033,7 @@ bare_label_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -16337,6 +17068,7 @@ bare_label_keyword: | STORAGE | STORED | STRICT_P + | STRING | STRIP_P | SUBSCRIPTION | SUBSTRING @@ -16370,6 +17102,7 @@ bare_label_keyword: | UESCAPE | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNIQUE | UNKNOWN diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 4133526f04..9db8967c60 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -690,6 +690,10 @@ assign_collations_walker(Node *node, assign_collations_context *context) &loccontext); } break; + case T_JsonExpr: + /* Context item and PASSING arguments are already + * marked with collations in parse_expr.c. */ + break; default: /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2d1a477154..678840593b 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -15,6 +15,8 @@ #include "postgres.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "miscadmin.h" @@ -34,6 +36,8 @@ #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/date.h" +#include "utils/fmgroids.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -74,6 +78,21 @@ static Node *transformWholeRowRef(ParseState *pstate, static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformJsonObjectConstructor(ParseState *pstate, + JsonObjectConstructor *ctor); +static Node *transformJsonArrayConstructor(ParseState *pstate, + JsonArrayConstructor *ctor); +static Node *transformJsonArrayQueryConstructor(ParseState *pstate, + JsonArrayQueryConstructor *ctor); +static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); +static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); +static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p); +static Node *transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *p); +static Node *transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve); +static Node *transformJsonParseExpr(ParseState *pstate, JsonParseExpr *expr); +static Node *transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *expr); +static Node *transformJsonSerializeExpr(ParseState *pstate, + JsonSerializeExpr *expr); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -301,6 +320,50 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_JsonObjectConstructor: + result = transformJsonObjectConstructor(pstate, (JsonObjectConstructor *) expr); + break; + + case T_JsonArrayConstructor: + result = transformJsonArrayConstructor(pstate, (JsonArrayConstructor *) expr); + break; + + case T_JsonArrayQueryConstructor: + result = transformJsonArrayQueryConstructor(pstate, (JsonArrayQueryConstructor *) expr); + break; + + case T_JsonObjectAgg: + result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr); + break; + + case T_JsonArrayAgg: + result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); + break; + + case T_JsonIsPredicate: + result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); + break; + + case T_JsonFuncExpr: + result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); + break; + + case T_JsonValueExpr: + result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr); + break; + + case T_JsonParseExpr: + result = transformJsonParseExpr(pstate, (JsonParseExpr *) expr); + break; + + case T_JsonScalarExpr: + result = transformJsonScalarExpr(pstate, (JsonScalarExpr *) expr); + break; + + case T_JsonSerializeExpr: + result = transformJsonSerializeExpr(pstate, (JsonSerializeExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3094,3 +3157,1411 @@ ParseExprKindName(ParseExprKind exprKind) } return "unrecognized expression kind"; } + +/* + * Make string Const node from JSON encoding name. + * + * UTF8 is default encoding. + */ +static Const * +getJsonEncodingConst(JsonFormat *format) +{ + JsonEncoding encoding; + const char *enc; + Name encname = palloc(sizeof(NameData)); + + if (!format || + format->format == JS_FORMAT_DEFAULT || + format->encoding == JS_ENC_DEFAULT) + encoding = JS_ENC_UTF8; + else + encoding = format->encoding; + + switch (encoding) + { + case JS_ENC_UTF16: + enc = "UTF16"; + break; + case JS_ENC_UTF32: + enc = "UTF32"; + break; + case JS_ENC_UTF8: + enc = "UTF8"; + break; + default: + elog(ERROR, "invalid JSON encoding: %d", encoding); + break; + } + + namestrcpy(encname, enc); + + return makeConst(NAMEOID, -1, InvalidOid, NAMEDATALEN, + NameGetDatum(encname), false, false); +} + +/* + * Make bytea => text conversion using specified JSON format encoding. + */ +static Node * +makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location) +{ + Const *encoding = getJsonEncodingConst(format); + FuncExpr *fexpr = makeFuncExpr(F_CONVERT_FROM, TEXTOID, + list_make2(expr, encoding), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + + fexpr->location = location; + + return (Node *) fexpr; +} + +/* + * Make CaseTestExpr node. + */ +static Node * +makeCaseTestExpr(Node *expr) +{ + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + + placeholder->typeId = exprType(expr); + placeholder->typeMod = exprTypmod(expr); + placeholder->collation = exprCollation(expr); + + return (Node *) placeholder; +} + +/* + * Transform JSON value expression using specified input JSON format or + * default format otherwise. + */ +static Node * +transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, + JsonFormatType default_format, bool isarg, + Oid targettype) +{ + Node *expr = transformExprRecurse(pstate, (Node *) ve->raw_expr); + Node *rawexpr; + JsonFormatType format; + Oid exprtype; + int location; + char typcategory; + bool typispreferred; + + if (exprType(expr) == UNKNOWNOID) + expr = coerce_to_specific_type(pstate, expr, TEXTOID, "JSON_VALUE_EXPR"); + + rawexpr = expr; + exprtype = exprType(expr); + location = exprLocation(expr); + + get_type_category_preferred(exprtype, &typcategory, &typispreferred); + + rawexpr = expr; + + if (ve->format->format != JS_FORMAT_DEFAULT) + { + if (ve->format->encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("JSON ENCODING clause is only allowed for bytea input type"), + parser_errposition(pstate, ve->format->location))); + + if (exprtype == JSONOID || exprtype == JSONBOID) + { + format = JS_FORMAT_DEFAULT; /* do not format json[b] types */ + ereport(WARNING, + (errmsg("FORMAT JSON has no effect for json and jsonb types"), + parser_errposition(pstate, ve->format->location))); + } + else + format = ve->format->format; + } + else if (isarg) + { + /* Pass SQL/JSON item types directly without conversion to json[b]. */ + switch (exprtype) + { + case TEXTOID: + case NUMERICOID: + case BOOLOID: + case INT2OID: + case INT4OID: + case INT8OID: + case FLOAT4OID: + case FLOAT8OID: + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + return expr; + + default: + if (typcategory == TYPCATEGORY_STRING) + return coerce_to_specific_type(pstate, expr, TEXTOID, + "JSON_VALUE_EXPR"); + /* else convert argument to json[b] type */ + break; + } + + format = default_format; + } + else if (exprtype == JSONOID || exprtype == JSONBOID) + format = JS_FORMAT_DEFAULT; /* do not format json[b] types */ + else + format = default_format; + + if (format == JS_FORMAT_DEFAULT && + (!OidIsValid(targettype) || exprtype == targettype)) + expr = rawexpr; + else + { + Node *orig = makeCaseTestExpr(expr); + Node *coerced; + bool cast_is_needed = OidIsValid(targettype); + + if (!isarg && !cast_is_needed && + exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg(ve->format->format == JS_FORMAT_DEFAULT ? + "cannot use non-string types with implicit FORMAT JSON clause" : + "cannot use non-string types with explicit FORMAT JSON clause"), + parser_errposition(pstate, ve->format->location >= 0 ? + ve->format->location : location))); + + expr = orig; + + /* Convert encoded JSON text from bytea. */ + if (format == JS_FORMAT_JSON && exprtype == BYTEAOID) + { + expr = makeJsonByteaToTextConversion(expr, ve->format, location); + exprtype = TEXTOID; + } + + if (!OidIsValid(targettype)) + targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + + /* Try to coerce to the target type. */ + coerced = coerce_to_target_type(pstate, expr, exprtype, + targettype, -1, + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + + if (!coerced) + { + /* If coercion failed, use to_json()/to_jsonb() functions. */ + FuncExpr *fexpr; + Oid fnoid; + + if (cast_is_needed) /* only CAST is allowed */ + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(exprtype), + format_type_be(targettype)), + parser_errposition(pstate, location))); + + fnoid = targettype == JSONOID ? F_TO_JSON : F_TO_JSONB; + fexpr = makeFuncExpr(fnoid, targettype, list_make1(expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + fexpr->location = location; + + coerced = (Node *) fexpr; + } + + if (coerced == orig) + expr = rawexpr; + else + { + ve = copyObject(ve); + ve->raw_expr = (Expr *) rawexpr; + ve->formatted_expr = (Expr *) coerced; + + expr = (Node *) ve; + } + } + + return expr; +} + +/* + * Transform JSON value expression using FORMAT JSON by default. + */ +static Node * +transformJsonValueExpr(ParseState *pstate, JsonValueExpr *jve) +{ + return transformJsonValueExprExt(pstate, jve, JS_FORMAT_JSON, false, + InvalidOid); +} + +/* + * Transform JSON value expression using unspecified format by default. + */ +static Node * +transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve) +{ + return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false, + InvalidOid); +} + +/* + * Checks specified output format for its applicability to the target type. + */ +static void +checkJsonOutputFormat(ParseState *pstate, const JsonFormat *format, + Oid targettype, bool allow_format_for_non_strings) +{ + if (!allow_format_for_non_strings && + format->format != JS_FORMAT_DEFAULT && + (targettype != BYTEAOID && + targettype != JSONOID && + targettype != JSONBOID)) + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(targettype, &typcategory, &typispreferred); + + if (typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot use JSON format with non-string output types"))); + } + + if (format->format == JS_FORMAT_JSON) + { + JsonEncoding enc = format->encoding != JS_ENC_DEFAULT ? + format->encoding : JS_ENC_UTF8; + + if (targettype != BYTEAOID && + format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot set JSON encoding for non-bytea output types"))); + + if (enc != JS_ENC_UTF8) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported JSON encoding"), + errhint("only UTF8 JSON encoding is supported"), + parser_errposition(pstate, format->location))); + } +} + +/* + * Transform JSON output clause. + * + * Assigns target type oid and modifier. + * Assigns default format or checks specified format for its applicability to + * the target type. + */ +static JsonReturning * +transformJsonOutput(ParseState *pstate, const JsonOutput *output, + bool allow_format) +{ + JsonReturning *ret; + + /* if output clause is not specified, make default clause value */ + if (!output) + { + ret = makeNode(JsonReturning); + + ret->format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + ret->typid = InvalidOid; + ret->typmod = -1; + + return ret; + } + + ret = copyObject(output->returning); + + typenameTypeIdAndMod(pstate, output->typeName, &ret->typid, &ret->typmod); + + if (output->typeName->setof) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("returning SETOF types is not supported in SQL/JSON functions"))); + + if (ret->format->format == JS_FORMAT_DEFAULT) + /* assign JSONB format when returning jsonb, or JSON format otherwise */ + ret->format->format = + ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + else + checkJsonOutputFormat(pstate, ret->format, ret->typid, allow_format); + + return ret; +} + +/* + * Transform JSON output clause of JSON contructor functions. + * + * Derive RETURNING type, if not specified, from argument types. + */ +static JsonReturning * +transformJsonConstructorOutput(ParseState *pstate, JsonOutput *output, + List *args) +{ + JsonReturning *returning = transformJsonOutput(pstate, output, true); + + if (!OidIsValid(returning->typid)) + { + ListCell *lc; + bool have_json = false; + bool have_jsonb = false; + + foreach(lc, args) + { + Node *expr = lfirst(lc); + Oid typid = exprType(expr); + + have_json |= typid == JSONOID; + have_jsonb |= typid == JSONBOID; + + if (have_jsonb) + break; + } + + if (have_jsonb) + { + returning->typid = JSONBOID; + returning->format->format = JS_FORMAT_JSONB; + } + else if (have_json) + { + returning->typid = JSONOID; + returning->format->format = JS_FORMAT_JSON; + } + else + { + /* XXX TEXT is default by the standard, but we return JSON */ + returning->typid = JSONOID; + returning->format->format = JS_FORMAT_JSON; + } + + returning->typmod = -1; + } + + return returning; +} + +/* + * Coerce json[b]-valued function expression to the output type. + */ +static Node * +coerceJsonFuncExpr(ParseState *pstate, Node *expr, + const JsonReturning *returning, bool report_error) +{ + Node *res; + int location; + Oid exprtype = exprType(expr); + + /* if output type is not specified or equals to function type, return */ + if (!OidIsValid(returning->typid) || returning->typid == exprtype) + return expr; + + location = exprLocation(expr); + + if (location < 0) + location = returning ? returning->format->location : -1; + + /* special case for RETURNING bytea FORMAT json */ + if (returning->format->format == JS_FORMAT_JSON && + returning->typid == BYTEAOID) + { + /* encode json text into bytea using pg_convert_to() */ + Node *texpr = coerce_to_specific_type(pstate, expr, TEXTOID, + "JSON_FUNCTION"); + Const *enc = getJsonEncodingConst(returning->format); + FuncExpr *fexpr = makeFuncExpr(F_CONVERT_TO, BYTEAOID, + list_make2(texpr, enc), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = location; + + return (Node *) fexpr; + } + + /* try to coerce expression to the output type */ + res = coerce_to_target_type(pstate, expr, exprtype, + returning->typid, returning->typmod, + /* XXX throwing errors when casting to char(N) */ + COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, + location); + + if (!res && report_error) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(exprtype), + format_type_be(returning->typid)), + parser_coercion_errposition(pstate, location, expr))); + + return res; +} + +static Node * +makeJsonConstructorExpr(ParseState *pstate, JsonConstructorType type, + List *args, Expr *fexpr, JsonReturning *returning, + bool unique, bool absent_on_null, int location) +{ + JsonConstructorExpr *jsctor = makeNode(JsonConstructorExpr); + Node *placeholder; + Node *coercion; + Oid intermediate_typid = + returning->format->format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + + jsctor->args = args; + jsctor->func = fexpr; + jsctor->type = type; + jsctor->returning = returning; + jsctor->unique = unique; + jsctor->absent_on_null = absent_on_null; + jsctor->location = location; + + if (fexpr) + placeholder = makeCaseTestExpr((Node *) fexpr); + else + { + CaseTestExpr *cte = makeNode(CaseTestExpr); + + cte->typeId = intermediate_typid; + cte->typeMod = -1; + cte->collation = InvalidOid; + + placeholder = (Node *) cte; + } + + coercion = coerceJsonFuncExpr(pstate, placeholder, returning, true); + + if (coercion != placeholder) + jsctor->coercion = (Expr *) coercion; + + return (Node *) jsctor; +} + +/* + * Transform JSON_OBJECT() constructor. + * + * JSON_OBJECT() is transformed into json[b]_build_object[_ext]() call + * depending on the output JSON format. The first two arguments of + * json[b]_build_object_ext() are absent_on_null and check_key_uniqueness. + * + * Then function call result is coerced to the target type. + */ +static Node * +transformJsonObjectConstructor(ParseState *pstate, JsonObjectConstructor *ctor) +{ + JsonReturning *returning; + List *args = NIL; + + /* transform key-value pairs, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* transform and append key-value arguments */ + foreach(lc, ctor->exprs) + { + JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc)); + Node *key = transformExprRecurse(pstate, (Node *) kv->key); + Node *val = transformJsonValueExprDefault(pstate, kv->value); + + args = lappend(args, key); + args = lappend(args, val); + } + } + + returning = transformJsonConstructorOutput(pstate, ctor->output, args); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_OBJECT, args, NULL, + returning, ctor->unique, + ctor->absent_on_null, ctor->location); +} + +/* + * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into + * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a)) + */ +static Node * +transformJsonArrayQueryConstructor(ParseState *pstate, + JsonArrayQueryConstructor *ctor) +{ + SubLink *sublink = makeNode(SubLink); + SelectStmt *select = makeNode(SelectStmt); + RangeSubselect *range = makeNode(RangeSubselect); + Alias *alias = makeNode(Alias); + ResTarget *target = makeNode(ResTarget); + JsonArrayAgg *agg = makeNode(JsonArrayAgg); + ColumnRef *colref = makeNode(ColumnRef); + Query *query; + ParseState *qpstate; + + /* Transform query only for counting target list entries. */ + qpstate = make_parsestate(pstate); + + query = transformStmt(qpstate, ctor->query); + + if (count_nonjunk_tlist_entries(query->targetList) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery must return only one column"), + parser_errposition(pstate, ctor->location))); + + free_parsestate(qpstate); + + colref->fields = list_make2(makeString(pstrdup("q")), + makeString(pstrdup("a"))); + colref->location = ctor->location; + + agg->arg = makeJsonValueExpr((Expr *) colref, ctor->format); + agg->absent_on_null = ctor->absent_on_null; + agg->constructor = makeNode(JsonAggConstructor); + agg->constructor->agg_order = NIL; + agg->constructor->output = ctor->output; + agg->constructor->location = ctor->location; + + target->name = NULL; + target->indirection = NIL; + target->val = (Node *) agg; + target->location = ctor->location; + + alias->aliasname = pstrdup("q"); + alias->colnames = list_make1(makeString(pstrdup("a"))); + + range->lateral = false; + range->subquery = ctor->query; + range->alias = alias; + + select->targetList = list_make1(target); + select->fromClause = list_make1(range); + + sublink->subLinkType = EXPR_SUBLINK; + sublink->subLinkId = 0; + sublink->testexpr = NULL; + sublink->operName = NIL; + sublink->subselect = (Node *) select; + sublink->location = ctor->location; + + return transformExprRecurse(pstate, (Node *) sublink); +} + +/* + * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation. + */ +static Node * +transformJsonAggConstructor(ParseState *pstate, JsonAggConstructor *agg_ctor, + JsonReturning *returning, List *args, + const char *aggfn, Oid aggtype, + JsonConstructorType ctor_type, + bool unique, bool absent_on_null) +{ + Oid aggfnoid; + Node *node; + Expr *aggfilter = agg_ctor->agg_filter ? (Expr *) + transformWhereClause(pstate, agg_ctor->agg_filter, + EXPR_KIND_FILTER, "FILTER") : NULL; + + aggfnoid = DatumGetInt32(DirectFunctionCall1(regprocin, + CStringGetDatum(aggfn))); + + if (agg_ctor->over) + { + /* window function */ + WindowFunc *wfunc = makeNode(WindowFunc); + + wfunc->winfnoid = aggfnoid; + wfunc->wintype = aggtype; + /* wincollid and inputcollid will be set by parse_collate.c */ + wfunc->args = args; + /* winref will be set by transformWindowFuncCall */ + wfunc->winstar = false; + wfunc->winagg = true; + wfunc->aggfilter = aggfilter; + wfunc->location = agg_ctor->location; + + /* + * ordered aggs not allowed in windows yet + */ + if (agg_ctor->agg_order != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("aggregate ORDER BY is not implemented for window functions"), + parser_errposition(pstate, agg_ctor->location))); + + /* parse_agg.c does additional window-func-specific processing */ + transformWindowFuncCall(pstate, wfunc, agg_ctor->over); + + node = (Node *) wfunc; + } + else + { + Aggref *aggref = makeNode(Aggref); + + aggref->aggfnoid = aggfnoid; + aggref->aggtype = aggtype; + + /* aggcollid and inputcollid will be set by parse_collate.c */ + aggref->aggtranstype = InvalidOid; /* will be set by planner */ + /* aggargtypes will be set by transformAggregateCall */ + /* aggdirectargs and args will be set by transformAggregateCall */ + /* aggorder and aggdistinct will be set by transformAggregateCall */ + aggref->aggfilter = aggfilter; + aggref->aggstar = false; + aggref->aggvariadic = false; + aggref->aggkind = AGGKIND_NORMAL; + /* agglevelsup will be set by transformAggregateCall */ + aggref->aggsplit = AGGSPLIT_SIMPLE; /* planner might change this */ + aggref->location = agg_ctor->location; + + transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false); + + node = (Node *) aggref; + } + + return makeJsonConstructorExpr(pstate, ctor_type, NIL, (Expr *) node, + returning, unique, absent_on_null, + agg_ctor->location); +} + +/* + * Transform JSON_OBJECTAGG() aggregate function. + * + * JSON_OBJECTAGG() is transformed into + * json[b]_objectagg(key, value, absent_on_null, check_unique) call depending on + * the output JSON format. Then the function call result is coerced to the + * target output type. + */ +static Node * +transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) +{ + JsonReturning *returning; + Node *key; + Node *val; + List *args; + const char *aggfnname; + Oid aggtype; + + key = transformExprRecurse(pstate, (Node *) agg->arg->key); + val = transformJsonValueExprDefault(pstate, agg->arg->value); + args = list_make2(key, val); + + returning = transformJsonConstructorOutput(pstate, agg->constructor->output, + args); + + if (returning->format->format == JS_FORMAT_JSONB) + { + if (agg->absent_on_null) + if (agg->unique) + aggfnname = "pg_catalog.jsonb_object_agg_unique_strict"; /* F_JSONB_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.jsonb_object_agg_strict"; /* F_JSONB_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.jsonb_object_agg_unique"; /* F_JSONB_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.jsonb_object_agg"; /* F_JSONB_OBJECT_AGG */ + + aggtype = JSONBOID; + } + else + { + if (agg->absent_on_null) + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique_strict"; /* F_JSON_OBJECT_AGG_UNIQUE_STRICT */ + else + aggfnname = "pg_catalog.json_object_agg_strict"; /* F_JSON_OBJECT_AGG_STRICT */ + else + if (agg->unique) + aggfnname = "pg_catalog.json_object_agg_unique"; /* F_JSON_OBJECT_AGG_UNIQUE */ + else + aggfnname = "pg_catalog.json_object_agg"; /* F_JSON_OBJECT_AGG */ + + aggtype = JSONOID; + } + + return transformJsonAggConstructor(pstate, agg->constructor, returning, + args, aggfnname, aggtype, + JSCTOR_JSON_OBJECTAGG, + agg->unique, agg->absent_on_null); +} + +/* + * Transform JSON_ARRAYAGG() aggregate function. + * + * JSON_ARRAYAGG() is transformed into json[b]_agg[_strict]() call depending + * on the output JSON format and absent_on_null. Then the function call result + * is coerced to the target output type. + */ +static Node * +transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) +{ + JsonReturning *returning; + Node *arg; + const char *aggfnname; + Oid aggtype; + + arg = transformJsonValueExprDefault(pstate, agg->arg); + + returning = transformJsonConstructorOutput(pstate, agg->constructor->output, + list_make1(arg)); + + if (returning->format->format == JS_FORMAT_JSONB) + { + aggfnname = agg->absent_on_null ? + "pg_catalog.jsonb_agg_strict" : "pg_catalog.jsonb_agg"; + aggtype = JSONBOID; + } + else + { + aggfnname = agg->absent_on_null ? + "pg_catalog.json_agg_strict" : "pg_catalog.json_agg"; + aggtype = JSONOID; + } + + return transformJsonAggConstructor(pstate, agg->constructor, returning, + list_make1(arg), aggfnname, aggtype, + JSCTOR_JSON_ARRAYAGG, + false, agg->absent_on_null); +} + +/* + * Transform JSON_ARRAY() constructor. + * + * JSON_ARRAY() is transformed into json[b]_build_array[_ext]() call + * depending on the output JSON format. The first argument of + * json[b]_build_array_ext() is absent_on_null. + * + * Then function call result is coerced to the target type. + */ +static Node * +transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) +{ + JsonReturning *returning; + List *args = NIL; + + /* transform element expressions, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* transform and append element arguments */ + foreach(lc, ctor->exprs) + { + JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); + Node *val = transformJsonValueExprDefault(pstate, jsval); + + args = lappend(args, val); + } + } + + returning = transformJsonConstructorOutput(pstate, ctor->output, args); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_ARRAY, args, NULL, + returning, false, ctor->absent_on_null, + ctor->location); +} + +static Node * +transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format, + Oid *exprtype) +{ + Node *raw_expr = transformExprRecurse(pstate, jsexpr); + Node *expr = raw_expr; + + *exprtype = exprType(expr); + + /* prepare input document */ + if (*exprtype == BYTEAOID) + { + JsonValueExpr *jve; + + expr = makeCaseTestExpr(raw_expr); + expr = makeJsonByteaToTextConversion(expr, format, exprLocation(expr)); + *exprtype = TEXTOID; + + jve = makeJsonValueExpr((Expr *) raw_expr, format); + + jve->formatted_expr = (Expr *) expr; + expr = (Node *) jve; + } + else + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(*exprtype, &typcategory, &typispreferred); + + if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING) + { + expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); + *exprtype = TEXTOID; + } + + if (format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types"))); + } + + return expr; +} + +/* + * Transform IS JSON predicate into + * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call. + */ +static Node * +transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) +{ + Oid exprtype; + Node *expr = transformJsonParseArg(pstate, pred->expr, pred->format, + &exprtype); + + /* make resulting expression */ + if (exprtype != TEXTOID && exprtype != JSONOID && exprtype != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use type %s in IS JSON predicate", + format_type_be(exprtype)))); + + return makeJsonIsPredicate(expr, NULL, pred->value_type, + pred->unique_keys, pred->location); +} + +/* + * Transform a JSON PASSING clause. + */ +static void +transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args, + List **passing_values, List **passing_names) +{ + ListCell *lc; + + *passing_values = NIL; + *passing_names = NIL; + + foreach(lc, args) + { + JsonArgument *arg = castNode(JsonArgument, lfirst(lc)); + Node *expr = transformJsonValueExprExt(pstate, arg->val, + format, true, InvalidOid); + + assign_expr_collations(pstate, expr); + + *passing_values = lappend(*passing_values, expr); + *passing_names = lappend(*passing_names, makeString(arg->name)); + } +} + +/* + * Transform a JSON BEHAVIOR clause. + */ +static JsonBehavior * +transformJsonBehavior(ParseState *pstate, JsonBehavior *behavior, + JsonBehaviorType default_behavior) +{ + JsonBehaviorType behavior_type; + Node *default_expr; + + behavior_type = behavior ? behavior->btype : default_behavior; + default_expr = behavior_type != JSON_BEHAVIOR_DEFAULT ? NULL : + transformExprRecurse(pstate, behavior->default_expr); + + return makeJsonBehavior(behavior_type, default_expr); +} + +/* + * Common code for JSON_VALUE, JSON_QUERY, JSON_EXISTS transformation + * into a JsonExpr node. + */ +static JsonExpr * +transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) +{ + JsonExpr *jsexpr = makeNode(JsonExpr); + Node *pathspec; + JsonFormatType format; + + if (func->common->pathname) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("JSON_TABLE path name is not allowed here"), + parser_errposition(pstate, func->location))); + + jsexpr->location = func->location; + jsexpr->op = func->op; + jsexpr->formatted_expr = transformJsonValueExpr(pstate, func->common->expr); + + assign_expr_collations(pstate, jsexpr->formatted_expr); + + /* format is determined by context item type */ + format = exprType(jsexpr->formatted_expr) == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + + jsexpr->result_coercion = NULL; + jsexpr->omit_quotes = false; + + jsexpr->format = func->common->expr->format; + + pathspec = transformExprRecurse(pstate, func->common->pathspec); + + jsexpr->path_spec = + coerce_to_target_type(pstate, pathspec, exprType(pathspec), + JSONPATHOID, -1, + COERCION_EXPLICIT, COERCE_IMPLICIT_CAST, + exprLocation(pathspec)); + if (!jsexpr->path_spec) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("JSON path expression must be type %s, not type %s", + "jsonpath", format_type_be(exprType(pathspec))), + parser_errposition(pstate, exprLocation(pathspec)))); + + /* transform and coerce to json[b] passing arguments */ + transformJsonPassingArgs(pstate, format, func->common->passing, + &jsexpr->passing_values, &jsexpr->passing_names); + + if (func->op != IS_JSON_EXISTS) + jsexpr->on_empty = transformJsonBehavior(pstate, func->on_empty, + JSON_BEHAVIOR_NULL); + + jsexpr->on_error = transformJsonBehavior(pstate, func->on_error, + func->op == IS_JSON_EXISTS ? + JSON_BEHAVIOR_FALSE : + JSON_BEHAVIOR_NULL); + + return jsexpr; +} + +/* + * Assign default JSON returning type from the specified format or from + * the context item type. + */ +static void +assignDefaultJsonReturningType(Node *context_item, JsonFormat *context_format, + JsonReturning *ret) +{ + bool is_jsonb; + + ret->format = copyObject(context_format); + + if (ret->format->format == JS_FORMAT_DEFAULT) + is_jsonb = exprType(context_item) == JSONBOID; + else + is_jsonb = ret->format->format == JS_FORMAT_JSONB; + + ret->typid = is_jsonb ? JSONBOID : JSONOID; + ret->typmod = -1; +} + +/* + * Try to coerce expression to the output type or + * use json_populate_type() for composite, array and domain types or + * use coercion via I/O. + */ +static JsonCoercion * +coerceJsonExpr(ParseState *pstate, Node *expr, const JsonReturning *returning) +{ + char typtype; + JsonCoercion *coercion = makeNode(JsonCoercion); + + coercion->expr = coerceJsonFuncExpr(pstate, expr, returning, false); + + if (coercion->expr) + { + if (coercion->expr == expr) + coercion->expr = NULL; + + return coercion; + } + + typtype = get_typtype(returning->typid); + + if (returning->typid == RECORDOID || + typtype == TYPTYPE_COMPOSITE || + typtype == TYPTYPE_DOMAIN || + type_is_array(returning->typid)) + coercion->via_populate = true; + else + coercion->via_io = true; + + return coercion; +} + +/* + * Transform a JSON output clause of JSON_VALUE and JSON_QUERY. + */ +static void +transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, + JsonExpr *jsexpr) +{ + Node *expr = jsexpr->formatted_expr; + + jsexpr->returning = transformJsonOutput(pstate, func->output, false); + + /* JSON_VALUE returns text by default */ + if (func->op == IS_JSON_VALUE && !OidIsValid(jsexpr->returning->typid)) + { + jsexpr->returning->typid = TEXTOID; + jsexpr->returning->typmod = -1; + } + + if (OidIsValid(jsexpr->returning->typid)) + { + JsonReturning ret; + + if (func->op == IS_JSON_VALUE && + jsexpr->returning->typid != JSONOID && + jsexpr->returning->typid != JSONBOID) + { + /* Forced coercion via I/O for JSON_VALUE for non-JSON types */ + jsexpr->result_coercion = makeNode(JsonCoercion); + jsexpr->result_coercion->expr = NULL; + jsexpr->result_coercion->via_io = true; + return; + } + + assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format, &ret); + + if (ret.typid != jsexpr->returning->typid || + ret.typmod != jsexpr->returning->typmod) + { + Node *placeholder = makeCaseTestExpr(expr); + + Assert(((CaseTestExpr *) placeholder)->typeId == ret.typid); + Assert(((CaseTestExpr *) placeholder)->typeMod == ret.typmod); + + jsexpr->result_coercion = coerceJsonExpr(pstate, placeholder, + jsexpr->returning); + } + } + else + assignDefaultJsonReturningType(jsexpr->formatted_expr, jsexpr->format, + jsexpr->returning); +} + +/* + * Coerce a expression in JSON DEFAULT behavior to the target output type. + */ +static Node * +coerceDefaultJsonExpr(ParseState *pstate, JsonExpr *jsexpr, Node *defexpr) +{ + int location; + Oid exprtype; + + if (!defexpr) + return NULL; + + exprtype = exprType(defexpr); + location = exprLocation(defexpr); + + if (location < 0) + location = jsexpr->location; + + defexpr = coerce_to_target_type(pstate, + defexpr, + exprtype, + jsexpr->returning->typid, + jsexpr->returning->typmod, + COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, + location); + + if (!defexpr) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast DEFAULT expression type %s to %s", + format_type_be(exprtype), + format_type_be(jsexpr->returning->typid)), + parser_errposition(pstate, location))); + + return defexpr; +} + +/* + * Initialize SQL/JSON item coercion from the SQL type "typid" to the target + * "returning" type. + */ +static JsonCoercion * +initJsonItemCoercion(ParseState *pstate, Oid typid, + const JsonReturning *returning) +{ + Node *expr; + + if (typid == UNKNOWNOID) + { + expr = (Node *) makeNullConst(UNKNOWNOID, -1, InvalidOid); + } + else + { + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + + placeholder->typeId = typid; + placeholder->typeMod = -1; + placeholder->collation = InvalidOid; + + expr = (Node *) placeholder; + } + + return coerceJsonExpr(pstate, expr, returning); +} + +static void +initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions, + const JsonReturning *returning, Oid contextItemTypeId) +{ + struct + { + JsonCoercion **coercion; + Oid typid; + } *p, + coercionTypids[] = + { + { &coercions->null, UNKNOWNOID }, + { &coercions->string, TEXTOID }, + { &coercions->numeric, NUMERICOID }, + { &coercions->boolean, BOOLOID }, + { &coercions->date, DATEOID }, + { &coercions->time, TIMEOID }, + { &coercions->timetz, TIMETZOID }, + { &coercions->timestamp, TIMESTAMPOID }, + { &coercions->timestamptz, TIMESTAMPTZOID }, + { &coercions->composite, contextItemTypeId }, + { NULL, InvalidOid } + }; + + for (p = coercionTypids; p->coercion; p++) + *p->coercion = initJsonItemCoercion(pstate, p->typid, returning); +} + +/* + * Transform JSON_VALUE, JSON_QUERY, JSON_EXISTS functions into a JsonExpr node. + */ +static Node * +transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) +{ + JsonExpr *jsexpr = transformJsonExprCommon(pstate, func); + const char *func_name = NULL; + Node *contextItemExpr = jsexpr->formatted_expr; + + switch (func->op) + { + case IS_JSON_VALUE: + func_name = "JSON_VALUE"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->returning->format->format = JS_FORMAT_DEFAULT; + jsexpr->returning->format->encoding = JS_ENC_DEFAULT; + + jsexpr->on_empty->default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_empty->default_expr); + + jsexpr->on_error->default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_error->default_expr); + + jsexpr->coercions = makeNode(JsonItemCoercions); + initJsonItemCoercions(pstate, jsexpr->coercions, jsexpr->returning, + exprType(contextItemExpr)); + + break; + + case IS_JSON_QUERY: + func_name = "JSON_QUERY"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->on_empty->default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_empty->default_expr); + + jsexpr->on_error->default_expr = + coerceDefaultJsonExpr(pstate, jsexpr, + jsexpr->on_error->default_expr); + + jsexpr->wrapper = func->wrapper; + jsexpr->omit_quotes = func->omit_quotes; + + break; + + case IS_JSON_EXISTS: + func_name = "JSON_EXISTS"; + + jsexpr->returning = transformJsonOutput(pstate, func->output, false); + + jsexpr->returning->format->format = JS_FORMAT_DEFAULT; + jsexpr->returning->format->encoding = JS_ENC_DEFAULT; + + if (!OidIsValid(jsexpr->returning->typid)) + { + jsexpr->returning->typid = BOOLOID; + jsexpr->returning->typmod = -1; + } + else if (jsexpr->returning->typid != BOOLOID) + { + CaseTestExpr *placeholder = makeNode(CaseTestExpr); + int location = exprLocation((Node *) jsexpr); + + placeholder->typeId = BOOLOID; + placeholder->typeMod = -1; + placeholder->collation = InvalidOid; + + jsexpr->result_coercion = makeNode(JsonCoercion); + jsexpr->result_coercion->expr = + coerce_to_target_type(pstate, (Node *) placeholder, BOOLOID, + jsexpr->returning->typid, + jsexpr->returning->typmod, + COERCION_EXPLICIT, + COERCE_IMPLICIT_CAST, + location); + + if (!jsexpr->result_coercion->expr) + ereport(ERROR, + (errcode(ERRCODE_CANNOT_COERCE), + errmsg("cannot cast type %s to %s", + format_type_be(BOOLOID), + format_type_be(jsexpr->returning->typid)), + parser_coercion_errposition(pstate, location, (Node *) jsexpr))); + + if (jsexpr->result_coercion->expr == (Node *) placeholder) + jsexpr->result_coercion->expr = NULL; + } + break; + } + + if (exprType(contextItemExpr) != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s() is not yet implemented for json type", func_name), + parser_errposition(pstate, func->location))); + + return (Node *) jsexpr; +} + +static JsonReturning * +transformJsonConstructorRet(ParseState *pstate, JsonOutput *output, const char *fname) +{ + JsonReturning *returning; + + if (output) + { + returning = transformJsonOutput(pstate, output, false); + + Assert(OidIsValid(returning->typid)); + + if (returning->typid != JSONOID && returning->typid != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use RETURNING type %s in %s", + format_type_be(returning->typid), fname), + parser_errposition(pstate, output->typeName->location))); + } + else + { + Oid targettype = SQLJSON_TYPE_OID(); + JsonFormatType format = + SQLJSON_TYPE_IS_JSONB() ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + + returning = makeNode(JsonReturning); + returning->format = makeJsonFormat(format, JS_ENC_DEFAULT, -1); + returning->typid = targettype; + returning->typmod = -1; + } + + return returning; +} + +/* + * Transform a JSON() expression. + */ +static Node * +transformJsonParseExpr(ParseState *pstate, JsonParseExpr *jsexpr) +{ + JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output, + "JSON()"); + Node *arg; + + if (jsexpr->unique_keys) + { + /* + * Coerce string argument to text and then to json[b] in the executor + * node with key uniqueness check. + */ + JsonValueExpr *jve = jsexpr->expr; + Oid arg_type; + + arg = transformJsonParseArg(pstate, (Node *) jve->raw_expr, jve->format, + &arg_type); + + if (arg_type != TEXTOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use non-string types with WITH UNIQUE KEYS clause"), + parser_errposition(pstate, jsexpr->location))); + } + else + { + /* + * Coerce argument to target type using CAST for compatibilty with PG + * function-like CASTs. + */ + arg = transformJsonValueExprExt(pstate, jsexpr->expr, JS_FORMAT_JSON, + false, returning->typid); + } + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_PARSE, list_make1(arg), NULL, + returning, jsexpr->unique_keys, false, + jsexpr->location); +} + +/* + * Transform a JSON_SCALAR() expression. + */ +static Node * +transformJsonScalarExpr(ParseState *pstate, JsonScalarExpr *jsexpr) +{ + Node *arg = transformExprRecurse(pstate, (Node *) jsexpr->expr); + JsonReturning *returning = transformJsonConstructorRet(pstate, jsexpr->output, + "JSON_SCALAR()"); + + if (exprType(arg) == UNKNOWNOID) + arg = coerce_to_specific_type(pstate, arg, TEXTOID, "JSON_SCALAR"); + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SCALAR, list_make1(arg), NULL, + returning, false, false, jsexpr->location); +} + +/* + * Transform a JSON_SERIALIZE() expression. + */ +static Node * +transformJsonSerializeExpr(ParseState *pstate, JsonSerializeExpr *expr) +{ + Node *arg = transformJsonValueExpr(pstate, expr->expr); + JsonReturning *returning; + + if (expr->output) + returning = transformJsonOutput(pstate, expr->output, true); + else + { + /* RETURNING TEXT FORMAT JSON is by default */ + returning = makeNode(JsonReturning); + returning->format = makeJsonFormat(JS_FORMAT_JSON, JS_ENC_DEFAULT, -1); + returning->typid = TEXTOID; + returning->typmod = -1; + } + + return makeJsonConstructorExpr(pstate, JSCTOR_JSON_SERIALIZE, list_make1(arg), + NULL, returning, false, false, expr->location); +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 6e8fbc4780..f390b29637 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1958,6 +1958,43 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_JsonParseExpr: + *name = "json"; + return 2; + case T_JsonScalarExpr: + *name = "json_scalar"; + return 2; + case T_JsonSerializeExpr: + *name = "json_serialize"; + return 2; + case T_JsonObjectConstructor: + *name = "json_object"; + return 2; + case T_JsonArrayConstructor: + case T_JsonArrayQueryConstructor: + *name = "json_array"; + return 2; + case T_JsonObjectAgg: + *name = "json_objectagg"; + return 2; + case T_JsonArrayAgg: + *name = "json_arrayagg"; + return 2; + case T_JsonFuncExpr: + /* make SQL/JSON functions act like a regular function */ + switch (((JsonFuncExpr *) node)->op) + { + case IS_JSON_QUERY: + *name = "json_query"; + return 2; + case IS_JSON_VALUE: + *name = "json_value"; + return 2; + case IS_JSON_EXISTS: + *name = "json_exists"; + return 2; + } + break; default: break; } diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 875de7ba28..683e1ebcfa 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -150,6 +150,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case USCONST: cur_token_length = strlen(yyextra->core_yy_extra.scanbuf + *llocp); break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -221,6 +224,19 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case ORDINALITY: cur_token = WITH_LA; break; + case UNIQUE: + cur_token = WITH_LA_UNIQUE; + break; + } + break; + + case WITHOUT: + /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */ + switch (next_token) + { + case TIME: + cur_token = WITHOUT_LA; + break; } break; diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index 0e8e065457..0f7646996a 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -23,6 +23,7 @@ #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/numeric.h" #include "utils/syscache.h" @@ -294,6 +295,14 @@ format_type_extended(Oid type_oid, int32 typemod, bits16 flags) else buf = pstrdup("character varying"); break; + + case JSONOID: + buf = pstrdup(SQLJSON_TYPE_IS_JSONB() ? "pg_catalog.json" : "json"); + break; + + case JSONBOID: + buf = pstrdup(SQLJSON_TYPE_IS_JSONB() ? "json" : "jsonb"); + break; } if (buf == NULL) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index a1145e2721..1f88f8d828 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -1018,11 +1018,6 @@ typedef struct NUMProc *L_currency_symbol; } NUMProc; -/* Return flags for DCH_from_char() */ -#define DCH_DATED 0x01 -#define DCH_TIMED 0x02 -#define DCH_ZONED 0x04 - /* ---------- * Functions * ---------- @@ -6673,3 +6668,43 @@ float8_to_char(PG_FUNCTION_ARGS) NUM_TOCHAR_finish; PG_RETURN_TEXT_P(result); } + +int +datetime_format_flags(const char *fmt_str, bool *have_error) +{ + bool incache; + int fmt_len = strlen(fmt_str); + int result; + FormatNode *format; + + if (fmt_len > DCH_CACHE_SIZE) + { + /* + * Allocate new memory if format picture is bigger than static cache + * and do not use cache (call parser always) + */ + incache = false; + + format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); + + parse_format(format, fmt_str, DCH_keywords, + DCH_suff, DCH_index, DCH_FLAG, NULL); + } + else + { + /* + * Use cache buffers + */ + DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false); + + incache = true; + format = ent->format; + } + + result = DCH_datetime_type(format, have_error); + + if (!incache) + pfree(format); + + return result; +} diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 30ca2cf6c8..3e5bb37369 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -13,7 +13,10 @@ */ #include "postgres.h" +#include "access/hash.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "common/hashfn.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" @@ -27,20 +30,41 @@ #include "utils/lsyscache.h" #include "utils/typcache.h" -typedef enum /* type categories for datum_to_json */ +/* Common context for key uniqueness check */ +typedef struct HTAB *JsonUniqueCheckState; /* hash table for key names */ + +/* Hash entry for JsonUniqueCheckState */ +typedef struct JsonUniqueHashEntry +{ + const char *key; + int key_len; + int object_id; +} JsonUniqueHashEntry; + +/* Context for key uniqueness check in builder functions */ +typedef struct JsonUniqueBuilderState +{ + JsonUniqueCheckState check; /* unique check */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueBuilderState; + +/* Element of object stack for key uniqueness check during json parsing */ +typedef struct JsonUniqueStackEntry { - JSONTYPE_NULL, /* null, so we didn't bother to identify */ - JSONTYPE_BOOL, /* boolean (built-in types only) */ - JSONTYPE_NUMERIC, /* numeric (ditto) */ - JSONTYPE_DATE, /* we use special formatting for datetimes */ - JSONTYPE_TIMESTAMP, - JSONTYPE_TIMESTAMPTZ, - JSONTYPE_JSON, /* JSON itself (and JSONB) */ - JSONTYPE_ARRAY, /* array */ - JSONTYPE_COMPOSITE, /* composite */ - JSONTYPE_CAST, /* something with an explicit cast to JSON */ - JSONTYPE_OTHER /* all else */ -} JsonTypeCategory; + struct JsonUniqueStackEntry *parent; + int object_id; +} JsonUniqueStackEntry; + +/* State for key uniqueness check during json parsing */ +typedef struct JsonUniqueParsingState +{ + JsonLexContext *lex; + JsonUniqueCheckState check; + JsonUniqueStackEntry *stack; + int id_counter; + bool unique; +} JsonUniqueParsingState; typedef struct JsonAggState { @@ -49,6 +73,7 @@ typedef struct JsonAggState Oid key_output_func; JsonTypeCategory val_category; Oid val_output_func; + JsonUniqueBuilderState unique_check; } JsonAggState; static void composite_to_json(Datum composite, StringInfo result, @@ -59,9 +84,6 @@ static void array_dim_to_json(StringInfo result, int dim, int ndims, int *dims, bool use_line_feeds); static void array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds); -static void json_categorize_type(Oid typoid, - JsonTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_json(Datum val, bool is_null, StringInfo result, JsonTypeCategory tcategory, Oid outfuncoid, bool key_scalar); @@ -140,7 +162,7 @@ json_recv(PG_FUNCTION_ARGS) * output function OID. If the returned category is JSONTYPE_CAST, we * return the OID of the type->JSON cast function instead. */ -static void +void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, Oid *outfuncoid) @@ -722,6 +744,48 @@ row_to_json_pretty(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +Datum +to_json_worker(Datum val, JsonTypeCategory tcategory, Oid outfuncoid) +{ + StringInfo result = makeStringInfo(); + + datum_to_json(val, false, result, tcategory, outfuncoid, false); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +bool +to_json_is_immutable(Oid typoid) +{ + JsonTypeCategory tcategory; + Oid outfuncoid; + + json_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONTYPE_BOOL: + case JSONTYPE_JSON: + return true; + + case JSONTYPE_DATE: + case JSONTYPE_TIMESTAMP: + case JSONTYPE_TIMESTAMPTZ: + return false; + + case JSONTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONTYPE_NUMERIC: + case JSONTYPE_CAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_json(anyvalue) */ @@ -730,7 +794,6 @@ to_json(PG_FUNCTION_ARGS) { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); - StringInfo result; JsonTypeCategory tcategory; Oid outfuncoid; @@ -742,11 +805,7 @@ to_json(PG_FUNCTION_ARGS) json_categorize_type(val_type, &tcategory, &outfuncoid); - result = makeStringInfo(); - - datum_to_json(val, false, result, tcategory, outfuncoid, false); - - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + PG_RETURN_DATUM(to_json_worker(val, tcategory, outfuncoid)); } /* @@ -754,8 +813,8 @@ to_json(PG_FUNCTION_ARGS) * * aggregate input column as a json array value. */ -Datum -json_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext aggcontext, oldcontext; @@ -795,9 +854,14 @@ json_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + + if (state->str->len > 1) + appendStringInfoString(state->str, ", "); + /* fast path for NULLs */ if (PG_ARGISNULL(1)) { @@ -809,7 +873,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) val = PG_GETARG_DATUM(1); /* add some whitespace if structured type and not first item */ - if (!PG_ARGISNULL(0) && + if (!PG_ARGISNULL(0) && state->str->len > 1 && (state->val_category == JSONTYPE_ARRAY || state->val_category == JSONTYPE_COMPOSITE)) { @@ -827,6 +891,25 @@ json_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } + +/* + * json_agg aggregate function + */ +Datum +json_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, false); +} + +/* + * json_agg_strict aggregate function + */ +Datum +json_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_agg_transfn_worker(fcinfo, true); +} + /* * json_agg final function */ @@ -850,18 +933,122 @@ json_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); } +/* Functions implementing hash table for key uniqueness check */ +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const JsonUniqueHashEntry *entry = (JsonUniqueHashEntry *) key; + uint32 hash = hash_bytes_uint32(entry->object_id); + + hash ^= hash_bytes((const unsigned char *) entry->key, entry->key_len); + + return DatumGetUInt32(hash); +} + +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + const JsonUniqueHashEntry *entry1 = (const JsonUniqueHashEntry *) key1; + const JsonUniqueHashEntry *entry2 = (const JsonUniqueHashEntry *) key2; + + if (entry1->object_id != entry2->object_id) + return entry1->object_id > entry2->object_id ? 1 : -1; + + if (entry1->key_len != entry2->key_len) + return entry1->key_len > entry2->key_len ? 1 : -1; + + return strncmp(entry1->key, entry2->key, entry1->key_len); +} + +/* Functions implementing object key uniqueness check */ +static void +json_unique_check_init(JsonUniqueCheckState *cxt) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(JsonUniqueHashEntry); + ctl.entrysize = sizeof(JsonUniqueHashEntry); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.match = json_unique_hash_match; + + *cxt = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION | HASH_COMPARE); +} + +static void +json_unique_check_free(JsonUniqueCheckState *cxt) +{ + hash_destroy(*cxt); +} + +static bool +json_unique_check_key(JsonUniqueCheckState *cxt, const char *key, int object_id) +{ + JsonUniqueHashEntry entry; + bool found; + + entry.key = key; + entry.key_len = strlen(key); + entry.object_id = object_id; + + (void) hash_search(*cxt, &entry, HASH_ENTER, &found); + + return !found; +} + +static void +json_unique_builder_init(JsonUniqueBuilderState *cxt) +{ + json_unique_check_init(&cxt->check); + cxt->mcxt = CurrentMemoryContext; + cxt->skipped_keys.data = NULL; +} + +static void +json_unique_builder_free(JsonUniqueBuilderState *cxt) +{ + json_unique_check_free(&cxt->check); + + if (cxt->skipped_keys.data) + pfree(cxt->skipped_keys.data); +} + +/* On-demand initialization of skipped_keys StringInfo structure */ +static StringInfo +json_unique_builder_get_skipped_keys(JsonUniqueBuilderState *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + + return out; +} + /* * json_object_agg transition function. * * aggregate two input columns as a single json object value. */ -Datum -json_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +json_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext aggcontext, oldcontext; JsonAggState *state; + StringInfo out; Datum arg; + bool skip; + int key_offset; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -882,6 +1069,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(aggcontext); state = (JsonAggState *) palloc(sizeof(JsonAggState)); state->str = makeStringInfo(); + if (unique_keys) + json_unique_builder_init(&state->unique_check); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -909,7 +1100,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -925,11 +1115,49 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* Skip null values if absent_on_null */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + PG_RETURN_POINTER(state); + + out = json_unique_builder_get_skipped_keys(&state->unique_check); + } + else + { + out = state->str; + + /* + * Append comma delimiter only if we have already outputted some fields + * after the initial string "{ ". + */ + if (out->len > 2) + appendStringInfoString(out, ", "); + } + arg = PG_GETARG_DATUM(1); - datum_to_json(arg, false, state->str, state->key_category, + key_offset = out->len; + + datum_to_json(arg, false, out, state->key_category, state->key_output_func, true); + if (unique_keys) + { + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&state->unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + PG_RETURN_POINTER(state); + } + appendStringInfoString(state->str, " : "); if (PG_ARGISNULL(2)) @@ -943,6 +1171,42 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * json_object_agg aggregate function + */ +Datum +json_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, false); +} + +/* + * json_object_agg_strict aggregate function + */ +Datum +json_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * json_object_agg_unique aggregate function + */ +Datum +json_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * json_object_agg_unique_strict aggregate function + */ +Datum +json_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, true, true); +} + /* * json_object_agg final function. */ @@ -960,6 +1224,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS) if (state == NULL) PG_RETURN_NULL(); + json_unique_builder_free(&state->unique_check); + /* Else return state with appropriate object terminator added */ PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); } @@ -984,25 +1250,14 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) return result; } -/* - * SQL function json_build_object(variadic "any") - */ Datum -json_build_object(PG_FUNCTION_ARGS) +json_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); + JsonUniqueBuilderState unique_check; if (nargs % 2 != 0) ereport(ERROR, @@ -1016,19 +1271,58 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '{'); + if (unique_keys) + json_unique_builder_init(&unique_check); + for (i = 0; i < nargs; i += 2) { - appendStringInfoString(result, sep); - sep = ", "; + StringInfo out; + bool skip; + int key_offset; + + /* Skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + if (skip) + { + /* If key uniqueness check is needed we must save skipped keys */ + if (!unique_keys) + continue; + + out = json_unique_builder_get_skipped_keys(&unique_check); + } + else + { + appendStringInfoString(result, sep); + sep = ", "; + out = result; + } /* process key */ if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument %d cannot be null", i + 1), + errmsg("argument %d cannot be null", i + 1), errhint("Object keys should be text."))); - add_json(args[i], false, result, types[i], true); + /* save key offset before key appending */ + key_offset = out->len; + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + const char *key = &out->data[key_offset]; + + if (!json_unique_check_key(&unique_check.check, key, 0)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", key))); + + if (skip) + continue; + } appendStringInfoString(result, " : "); @@ -1038,7 +1332,29 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '}'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + if (unique_keys) + json_unique_builder_free(&unique_check); + + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1050,25 +1366,13 @@ json_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); } -/* - * SQL function json_build_array(variadic "any") - */ Datum -json_build_array(PG_FUNCTION_ARGS) +json_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; const char *sep = ""; StringInfo result; - Datum *args; - bool *nulls; - Oid *types; - - /* fetch argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); result = makeStringInfo(); @@ -1076,6 +1380,9 @@ json_build_array(PG_FUNCTION_ARGS) for (i = 0; i < nargs; i++) { + if (absent_on_null && nulls[i]) + continue; + appendStringInfoString(result, sep); sep = ", "; add_json(args[i], nulls[i], result, types[i], false); @@ -1083,7 +1390,26 @@ json_build_array(PG_FUNCTION_ARGS) appendStringInfoChar(result, ']'); - PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); + return PointerGetDatum(cstring_to_text_with_len(result->data, result->len)); +} + +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(json_build_array_worker(nargs, args, nulls, types, false)); } /* @@ -1317,6 +1643,106 @@ escape_json(StringInfo buf, const char *str) appendStringInfoCharMacro(buf, '"'); } +/* Semantic actions for key uniqueness check */ +static void +json_unique_object_start(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return; + + /* push object entry to stack */ + entry = palloc(sizeof(*entry)); + entry->object_id = state->id_counter++; + entry->parent = state->stack; + state->stack = entry; +} + +static void +json_unique_object_end(void *_state) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return; + + entry = state->stack; + state->stack = entry->parent; /* pop object from stack */ + pfree(entry); +} + +static void +json_unique_object_field_start(void *_state, char *field, bool isnull) +{ + JsonUniqueParsingState *state = _state; + JsonUniqueStackEntry *entry; + + if (!state->unique) + return; + + /* find key collision in the current object */ + if (json_unique_check_key(&state->check, field, state->stack->object_id)) + return; + + state->unique = false; + + /* pop all objects entries */ + while ((entry = state->stack)) + { + state->stack = entry->parent; + pfree(entry); + } +} + +/* Validate JSON text and additionally check key uniqueness */ +bool +json_validate(text *json, bool check_unique_keys, bool throw_error) +{ + JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys); + JsonSemAction uniqueSemAction = {0}; + JsonUniqueParsingState state; + JsonParseErrorType result; + + if (check_unique_keys) + { + state.lex = lex; + state.stack = NULL; + state.id_counter = 0; + state.unique = true; + json_unique_check_init(&state.check); + + uniqueSemAction.semstate = &state; + uniqueSemAction.object_start = json_unique_object_start; + uniqueSemAction.object_field_start = json_unique_object_field_start; + uniqueSemAction.object_end = json_unique_object_end; + } + + result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction); + + if (result != JSON_SUCCESS) + { + if (throw_error) + json_ereport_error(result, lex); + + return false; /* invalid json */ + } + + if (check_unique_keys && !state.unique) + { + if (throw_error) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + return false; /* not unique keys */ + } + + return true; /* ok */ +} + /* * SQL function json_typeof(json) -> text * @@ -1332,21 +1758,13 @@ escape_json(StringInfo buf, const char *str) Datum json_typeof(PG_FUNCTION_ARGS) { - text *json; - - JsonLexContext *lex; - JsonTokenType tok; + text *json = PG_GETARG_TEXT_PP(0); char *type; - JsonParseErrorType result; - - json = PG_GETARG_TEXT_PP(0); - lex = makeJsonLexContext(json, false); + JsonTokenType tok; /* Lex exactly one token from the input and check its type. */ - result = json_lex(lex); - if (result != JSON_SUCCESS) - json_ereport_error(result, lex); - tok = lex->token_type; + tok = json_get_first_token(json, true); + switch (tok) { case JSON_TOKEN_OBJECT_START: diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 8d1e7fbf91..d11683c962 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -14,6 +14,7 @@ #include "access/htup_details.h" #include "access/transam.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "libpq/pqformat.h" @@ -33,25 +34,9 @@ typedef struct JsonbInState { JsonbParseState *parseState; JsonbValue *res; + bool unique_keys; } JsonbInState; -/* unlike with json categories, we need to treat json and jsonb differently */ -typedef enum /* type categories for datum_to_jsonb */ -{ - JSONBTYPE_NULL, /* null, so we didn't bother to identify */ - JSONBTYPE_BOOL, /* boolean (built-in types only) */ - JSONBTYPE_NUMERIC, /* numeric (ditto) */ - JSONBTYPE_DATE, /* we use special formatting for datetimes */ - JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ - JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ - JSONBTYPE_JSON, /* JSON */ - JSONBTYPE_JSONB, /* JSONB */ - JSONBTYPE_ARRAY, /* array */ - JSONBTYPE_COMPOSITE, /* composite */ - JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ - JSONBTYPE_OTHER /* all else */ -} JsonbTypeCategory; - typedef struct JsonbAggState { JsonbInState *res; @@ -61,7 +46,7 @@ typedef struct JsonbAggState Oid val_output_func; } JsonbAggState; -static inline Datum jsonb_from_cstring(char *json, int len); +static inline Datum jsonb_from_cstring(char *json, int len, bool unique_keys); static size_t checkStringLen(size_t len); static void jsonb_in_object_start(void *pstate); static void jsonb_in_object_end(void *pstate); @@ -70,17 +55,11 @@ static void jsonb_in_array_end(void *pstate); static void jsonb_in_object_field_start(void *pstate, char *fname, bool isnull); static void jsonb_put_escaped_value(StringInfo out, JsonbValue *scalarVal); static void jsonb_in_scalar(void *pstate, char *token, JsonTokenType tokentype); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void composite_to_jsonb(Datum composite, JsonbInState *result); static void array_dim_to_jsonb(JsonbInState *result, int dim, int ndims, int *dims, Datum *vals, bool *nulls, int *valcount, JsonbTypeCategory tcategory, Oid outfuncoid); static void array_to_jsonb_internal(Datum array, JsonbInState *result); -static void jsonb_categorize_type(Oid typoid, - JsonbTypeCategory *tcategory, - Oid *outfuncoid); static void datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, JsonbTypeCategory tcategory, Oid outfuncoid, bool key_scalar); @@ -90,6 +69,8 @@ static JsonbParseState *clone_parse_state(JsonbParseState *state); static char *JsonbToCStringWorker(StringInfo out, JsonbContainer *in, int estimated_len, bool indent); static void add_indent(StringInfo out, bool indent, int level); +int sql_json_type; /* GUC for mapping jsonb to SQL/JSON JSON */ + /* * jsonb type input function */ @@ -98,7 +79,7 @@ jsonb_in(PG_FUNCTION_ARGS) { char *json = PG_GETARG_CSTRING(0); - return jsonb_from_cstring(json, strlen(json)); + return jsonb_from_cstring(json, strlen(json), false); } /* @@ -122,7 +103,7 @@ jsonb_recv(PG_FUNCTION_ARGS) else elog(ERROR, "unsupported jsonb version number %d", version); - return jsonb_from_cstring(str, nbytes); + return jsonb_from_cstring(str, nbytes, false); } /* @@ -163,6 +144,14 @@ jsonb_send(PG_FUNCTION_ARGS) PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } +Datum +jsonb_from_text(text *js, bool unique_keys) +{ + return jsonb_from_cstring(VARDATA_ANY(js), + VARSIZE_ANY_EXHDR(js), + unique_keys); +} + /* * Get the type name of a jsonb container. */ @@ -253,7 +242,7 @@ jsonb_typeof(PG_FUNCTION_ARGS) * Uses the json parser (with hooks) to construct a jsonb. */ static inline Datum -jsonb_from_cstring(char *json, int len) +jsonb_from_cstring(char *json, int len, bool unique_keys) { JsonLexContext *lex; JsonbInState state; @@ -263,6 +252,8 @@ jsonb_from_cstring(char *json, int len) memset(&sem, 0, sizeof(sem)); lex = makeJsonLexContextCstringLen(json, len, GetDatabaseEncoding(), true); + state.unique_keys = unique_keys; + sem.semstate = (void *) &state; sem.object_start = jsonb_in_object_start; @@ -297,6 +288,7 @@ jsonb_in_object_start(void *pstate) JsonbInState *_state = (JsonbInState *) pstate; _state->res = pushJsonbValue(&_state->parseState, WJB_BEGIN_OBJECT, NULL); + _state->parseState->unique_keys = _state->unique_keys; } static void @@ -619,7 +611,7 @@ add_indent(StringInfo out, bool indent, int level) * output function OID. If the returned category is JSONBTYPE_JSONCAST, * we return the OID of the relevant cast function instead. */ -static void +void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, Oid *outfuncoid) @@ -1126,6 +1118,51 @@ add_jsonb(Datum val, bool is_null, JsonbInState *result, datum_to_jsonb(val, is_null, result, tcategory, outfuncoid, key_scalar); } +Datum +to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, Oid outfuncoid) +{ + JsonbInState result; + + memset(&result, 0, sizeof(JsonbInState)); + + datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); + + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +bool +to_jsonb_is_immutable(Oid typoid) +{ + JsonbTypeCategory tcategory; + Oid outfuncoid; + + jsonb_categorize_type(typoid, &tcategory, &outfuncoid); + + switch (tcategory) + { + case JSONBTYPE_BOOL: + case JSONBTYPE_JSON: + case JSONBTYPE_JSONB: + return true; + + case JSONBTYPE_DATE: + case JSONBTYPE_TIMESTAMP: + case JSONBTYPE_TIMESTAMPTZ: + return false; + + case JSONBTYPE_ARRAY: + return false; /* TODO recurse into elements */ + + case JSONBTYPE_COMPOSITE: + return false; /* TODO recurse into fields */ + + case JSONBTYPE_NUMERIC: + case JSONBTYPE_JSONCAST: + default: + return func_volatile(outfuncoid) == PROVOLATILE_IMMUTABLE; + } +} + /* * SQL function to_jsonb(anyvalue) */ @@ -1134,7 +1171,6 @@ to_jsonb(PG_FUNCTION_ARGS) { Datum val = PG_GETARG_DATUM(0); Oid val_type = get_fn_expr_argtype(fcinfo->flinfo, 0); - JsonbInState result; JsonbTypeCategory tcategory; Oid outfuncoid; @@ -1146,31 +1182,15 @@ to_jsonb(PG_FUNCTION_ARGS) jsonb_categorize_type(val_type, &tcategory, &outfuncoid); - memset(&result, 0, sizeof(JsonbInState)); - - datum_to_jsonb(val, false, &result, tcategory, outfuncoid, false); - - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + PG_RETURN_DATUM(to_jsonb_worker(val, tcategory, outfuncoid)); } -/* - * SQL function jsonb_build_object(variadic "any") - */ Datum -jsonb_build_object(PG_FUNCTION_ARGS) +jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null, bool unique_keys) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); if (nargs % 2 != 0) ereport(ERROR, @@ -1183,15 +1203,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + result.parseState->unique_keys = unique_keys; + result.parseState->skip_nulls = absent_on_null; for (i = 0; i < nargs; i += 2) { /* process key */ + bool skip; + if (nulls[i]) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %d: key must not be null", i + 1))); + /* skip null values if absent_on_null */ + skip = absent_on_null && nulls[i + 1]; + + /* we need to save skipped keys for the key uniqueness check */ + if (skip && !unique_keys) + continue; + add_jsonb(args[i], false, &result, types[i], true); /* process value */ @@ -1200,7 +1231,26 @@ jsonb_build_object(PG_FUNCTION_ARGS) result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); +} + +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_object_worker(nargs, args, nulls, types, false, false)); } /* @@ -1219,37 +1269,50 @@ jsonb_build_object_noargs(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } -/* - * SQL function jsonb_build_array(variadic "any") - */ Datum -jsonb_build_array(PG_FUNCTION_ARGS) +jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, Oid *types, + bool absent_on_null) { - int nargs; int i; JsonbInState result; - Datum *args; - bool *nulls; - Oid *types; - - /* build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - if (nargs < 0) - PG_RETURN_NULL(); memset(&result, 0, sizeof(JsonbInState)); result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_ARRAY, NULL); for (i = 0; i < nargs; i++) + { + if (absent_on_null && nulls[i]) + continue; + add_jsonb(args[i], nulls[i], &result, types[i], false); + } result.res = pushJsonbValue(&result.parseState, WJB_END_ARRAY, NULL); - PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); + return JsonbPGetDatum(JsonbValueToJsonb(result.res)); } +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + Datum *args; + bool *nulls; + Oid *types; + /* build argument values to build the object */ + int nargs = extract_variadic_args(fcinfo, 0, true, + &args, &types, &nulls); + + if (nargs < 0) + PG_RETURN_NULL(); + + PG_RETURN_DATUM(jsonb_build_array_worker(nargs, args, nulls, types, false)); +} + + /* * degenerate case of jsonb_build_array where it gets 0 arguments. */ @@ -1490,6 +1553,8 @@ clone_parse_state(JsonbParseState *state) { ocursor->contVal = icursor->contVal; ocursor->size = icursor->size; + ocursor->unique_keys = icursor->unique_keys; + ocursor->skip_nulls = icursor->skip_nulls; icursor = icursor->next; if (icursor == NULL) break; @@ -1501,12 +1566,8 @@ clone_parse_state(JsonbParseState *state) return result; } - -/* - * jsonb_agg aggregate function - */ -Datum -jsonb_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_agg_transfn_worker(FunctionCallInfo fcinfo, bool absent_on_null) { MemoryContext oldcontext, aggcontext; @@ -1554,6 +1615,9 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) result = state->res; } + if (absent_on_null && PG_ARGISNULL(1)) + PG_RETURN_POINTER(state); + /* turn the argument into jsonb in the normal function context */ val = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1); @@ -1623,6 +1687,24 @@ jsonb_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_agg aggregate function + */ +Datum +jsonb_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, false); +} + +/* + * jsonb_agg_strict aggregate function + */ +Datum +jsonb_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_agg_transfn_worker(fcinfo, true); +} + Datum jsonb_agg_finalfn(PG_FUNCTION_ARGS) { @@ -1655,11 +1737,9 @@ jsonb_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(out); } -/* - * jsonb_object_agg aggregate function - */ -Datum -jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +static Datum +jsonb_object_agg_transfn_worker(FunctionCallInfo fcinfo, + bool absent_on_null, bool unique_keys) { MemoryContext oldcontext, aggcontext; @@ -1673,6 +1753,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1692,6 +1773,9 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + result->parseState->unique_keys = unique_keys; + result->parseState->skip_nulls = absent_on_null; + MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -1727,6 +1811,15 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("field name must not be null"))); + /* + * Skip null values if absent_on_null unless key uniqueness check is + * needed (because we must save keys in this case). + */ + skip = absent_on_null && PG_ARGISNULL(2); + + if (skip && !unique_keys) + PG_RETURN_POINTER(state); + val = PG_GETARG_DATUM(1); memset(&elem, 0, sizeof(JsonbInState)); @@ -1782,6 +1875,16 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + if (skip) + { + v.type = jbvNull; + result->res = pushJsonbValue(&result->parseState, + WJB_VALUE, &v); + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + break; case WJB_END_ARRAY: break; @@ -1854,6 +1957,43 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(state); } +/* + * jsonb_object_agg aggregate function + */ +Datum +jsonb_object_agg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, false); +} + + +/* + * jsonb_object_agg_strict aggregate function + */ +Datum +jsonb_object_agg_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, false); +} + +/* + * jsonb_object_agg_unique aggregate function + */ +Datum +jsonb_object_agg_unique_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, false, true); +} + +/* + * jsonb_object_agg_unique_strict aggregate function + */ +Datum +jsonb_object_agg_unique_strict_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, true, true); +} + Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { @@ -2085,3 +2225,65 @@ jsonb_float8(PG_FUNCTION_ARGS) PG_RETURN_DATUM(retValue); } + +/* + * Construct an empty array jsonb. + */ +Jsonb * +JsonbMakeEmptyArray(void) +{ + JsonbValue jbv; + + jbv.type = jbvArray; + jbv.val.array.elems = NULL; + jbv.val.array.nElems = 0; + jbv.val.array.rawScalar = false; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Construct an empty object jsonb. + */ +Jsonb * +JsonbMakeEmptyObject(void) +{ + JsonbValue jbv; + + jbv.type = jbvObject; + jbv.val.object.pairs = NULL; + jbv.val.object.nPairs = 0; + + return JsonbValueToJsonb(&jbv); +} + +/* + * Convert jsonb to a C-string stripping quotes from scalar strings. + */ +char * +JsonbUnquote(Jsonb *jb) +{ + if (JB_ROOT_IS_SCALAR(jb)) + { + JsonbValue v; + + JsonbExtractScalar(&jb->root, &v); + + if (v.type == jbvString) + return pnstrdup(v.val.string.val, v.val.string.len); + else if (v.type == jbvBool) + return pstrdup(v.val.boolean ? "true" : "false"); + else if (v.type == jbvNumeric) + return DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(v.val.numeric))); + else if (v.type == jbvNull) + return pstrdup("null"); + else + { + elog(ERROR, "unrecognized jsonb value type %d", v.type); + return NULL; + } + } + else + return JsonbToCString(NULL, &jb->root, VARSIZE(jb)); +} diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 5711187795..a4117617d4 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -63,7 +63,8 @@ static int lengthCompareJsonbStringValue(const void *a, const void *b); static int lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); -static void uniqueifyJsonbObject(JsonbValue *object); +static void uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, + bool skip_nulls); static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *scalarVal); @@ -688,7 +689,9 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, appendElement(*pstate, scalarVal); break; case WJB_END_OBJECT: - uniqueifyJsonbObject(&(*pstate)->contVal); + uniqueifyJsonbObject(&(*pstate)->contVal, + (*pstate)->unique_keys, + (*pstate)->skip_nulls); /* fall through! */ case WJB_END_ARRAY: /* Steps here common to WJB_END_OBJECT case */ @@ -731,6 +734,9 @@ pushState(JsonbParseState **pstate) JsonbParseState *ns = palloc(sizeof(JsonbParseState)); ns->next = *pstate; + ns->unique_keys = false; + ns->skip_nulls = false; + return ns; } @@ -1935,7 +1941,7 @@ lengthCompareJsonbPair(const void *a, const void *b, void *binequal) * Sort and unique-ify pairs in JsonbValue object */ static void -uniqueifyJsonbObject(JsonbValue *object) +uniqueifyJsonbObject(JsonbValue *object, bool unique_keys, bool skip_nulls) { bool hasNonUniq = false; @@ -1945,15 +1951,21 @@ uniqueifyJsonbObject(JsonbValue *object) qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair), lengthCompareJsonbPair, &hasNonUniq); - if (hasNonUniq) + if (hasNonUniq && unique_keys) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON object key value"))); + + if (hasNonUniq || skip_nulls) { JsonbPair *ptr = object->val.object.pairs + 1, *res = object->val.object.pairs; while (ptr - object->val.object.pairs < object->val.object.nPairs) { - /* Avoid copying over duplicate */ - if (lengthCompareJsonbStringValue(ptr, res) != 0) + /* Avoid copying over duplicate or null */ + if (lengthCompareJsonbStringValue(ptr, res) != 0 && + (!skip_nulls || ptr->value.type != jbvNull)) { res++; if (ptr != res) diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 6335845d08..7e1cb032f8 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -2771,11 +2771,11 @@ populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ check_stack_depth(); - if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + if (jbv->type != jbvBinary || + !JsonContainerIsArray(jbc) || + JsonContainerIsScalar(jbc)) populate_array_report_expected_array(ctx, ndim - 1); - Assert(!JsonContainerIsScalar(jbc)); - it = JsonbIteratorInit(jbc); tok = JsonbIteratorNext(&it, &val, true); @@ -3247,6 +3247,50 @@ populate_record_field(ColumnIOData *col, } } +/* recursively populate specified type from a json/jsonb value */ +Datum +json_populate_type(Datum json_val, Oid json_type, Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull) +{ + JsValue jsv = { 0 }; + JsonbValue jbv; + + jsv.is_json = json_type == JSONOID; + + if (*isnull) + { + if (jsv.is_json) + jsv.val.json.str = NULL; + else + jsv.val.jsonb = NULL; + } + else if (jsv.is_json) + { + text *json = DatumGetTextPP(json_val); + + jsv.val.json.str = VARDATA_ANY(json); + jsv.val.json.len = VARSIZE_ANY_EXHDR(json); + jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in populate_composite() */ + } + else + { + Jsonb *jsonb = DatumGetJsonbP(json_val); + + jsv.val.jsonb = &jbv; + + /* fill binary jsonb value pointing to jb */ + jbv.type = jbvBinary; + jbv.val.binary.data = &jsonb->root; + jbv.val.binary.len = VARSIZE(jsonb) - VARHDRSZ; + } + + if (!*cache) + *cache = MemoryContextAllocZero(mcxt, sizeof(ColumnIOData)); + + return populate_record_field(*cache , typid, typmod, NULL, mcxt, + PointerGetDatum(NULL), &jsv, isnull); +} + static RecordIOData * allocate_record_info(MemoryContext mcxt, int ncolumns) { @@ -5638,3 +5682,23 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype else appendStringInfoString(_state->strval, token); } + +JsonTokenType +json_get_first_token(text *json, bool throw_error) +{ + JsonLexContext *lex; + JsonParseErrorType result; + + lex = makeJsonLexContext(json, false); + + /* Lex exactly one token from the input and check its type. */ + result = json_lex(lex); + + if (result == JSON_SUCCESS) + return lex->token_type; + + if (throw_error) + json_ereport_error(result, lex); + + return JSON_TOKEN_INVALID; /* invalid json */ +} diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index fa22546f22..43c9871506 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -67,7 +67,9 @@ #include "lib/stringinfo.h" #include "libpq/pqformat.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "utils/builtins.h" +#include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" @@ -1073,3 +1075,258 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, return true; } + +/* SQL/JSON datatype status: */ +typedef enum JsonPathDatatypeStatus +{ + jpdsNonDateTime, /* null, bool, numeric, string, array, object */ + jpdsUnknownDateTime, /* unknown datetime type */ + jpdsDateTimeZoned, /* timetz, timestamptz */ + jpdsDateTimeNonZoned /* time, timestamp, date */ +} JsonPathDatatypeStatus; + +/* Context for jspIsMutableWalker() */ +typedef struct JsonPathMutableContext +{ + List *varnames; /* list of variable names */ + List *varexprs; /* list of variable expressions */ + JsonPathDatatypeStatus current; /* status of @ item */ + bool lax; /* jsonpath is lax or strict */ + bool mutable; /* resulting mutability status */ +} JsonPathMutableContext; + +/* + * Recursive walker for jspIsMutable() + */ +static JsonPathDatatypeStatus +jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt) +{ + JsonPathItem next; + JsonPathDatatypeStatus status = jpdsNonDateTime; + + while (!cxt->mutable) + { + JsonPathItem arg; + JsonPathDatatypeStatus leftStatus; + JsonPathDatatypeStatus rightStatus; + + switch (jpi->type) + { + case jpiRoot: + Assert(status == jpdsNonDateTime); + break; + + case jpiCurrent: + Assert(status == jpdsNonDateTime); + status = cxt->current; + break; + + case jpiFilter: + { + JsonPathDatatypeStatus prevStatus = cxt->current; + + cxt->current = status; + jspGetArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + + cxt->current = prevStatus; + break; + } + + case jpiVariable: + { + int32 len; + const char *name = jspGetString(jpi, &len); + ListCell *lc1; + ListCell *lc2; + + Assert(status == jpdsNonDateTime); + + forboth(lc1, cxt->varnames, lc2, cxt->varexprs) + { + String *varname = lfirst_node(String, lc1); + Node *varexpr = lfirst(lc2); + + if (strncmp(varname->val, name, len)) + continue; + + switch (exprType(varexpr)) + { + case DATEOID: + case TIMEOID: + case TIMESTAMPOID: + status = jpdsDateTimeNonZoned; + break; + + case TIMETZOID: + case TIMESTAMPTZOID: + status = jpdsDateTimeZoned; + break; + + default: + status = jpdsNonDateTime; + break; + } + + break; + } + break; + } + + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + Assert(status == jpdsNonDateTime); + jspGetLeftArg(jpi, &arg); + leftStatus = jspIsMutableWalker(&arg, cxt); + + jspGetRightArg(jpi, &arg); + rightStatus = jspIsMutableWalker(&arg, cxt); + + /* + * Comparison of datetime type with different timezone status + * is mutable. + */ + if (leftStatus != jpdsNonDateTime && + rightStatus != jpdsNonDateTime && + (leftStatus == jpdsUnknownDateTime || + rightStatus == jpdsUnknownDateTime || + leftStatus != rightStatus)) + cxt->mutable = true; + break; + + case jpiNot: + case jpiIsUnknown: + case jpiExists: + case jpiPlus: + case jpiMinus: + Assert(status == jpdsNonDateTime); + jspGetArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + break; + + case jpiAnd: + case jpiOr: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiStartsWith: + Assert(status == jpdsNonDateTime); + jspGetLeftArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + jspGetRightArg(jpi, &arg); + jspIsMutableWalker(&arg, cxt); + break; + + case jpiIndexArray: + for (int i = 0; i < jpi->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + + if (jspGetArraySubscript(jpi, &from, &to, i)) + jspIsMutableWalker(&to, cxt); + + jspIsMutableWalker(&from, cxt); + } + /* FALLTHROUGH */ + + case jpiAnyArray: + if (!cxt->lax) + status = jpdsNonDateTime; + break; + + case jpiAny: + if (jpi->content.anybounds.first > 0) + status = jpdsNonDateTime; + break; + + case jpiDatetime: + if (jpi->content.arg) + { + char *template; + int flags; + + jspGetArg(jpi, &arg); + if (arg.type != jpiString) + { + status = jpdsNonDateTime; + break; /* there will be runtime error */ + } + + template = jspGetString(&arg, NULL); + flags = datetime_format_flags(template, NULL); + if (flags & DCH_ZONED) + status = jpdsDateTimeZoned; + else + status = jpdsDateTimeNonZoned; + } + else + { + status = jpdsUnknownDateTime; + } + break; + + case jpiLikeRegex: + Assert(status == jpdsNonDateTime); + jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr); + jspIsMutableWalker(&arg, cxt); + break; + + /* literals */ + case jpiNull: + case jpiString: + case jpiNumeric: + case jpiBool: + /* accessors */ + case jpiKey: + case jpiAnyKey: + /* special items */ + case jpiSubscript: + case jpiLast: + /* item methods */ + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiKeyValue: + status = jpdsNonDateTime; + break; + } + + if (!jspGetNext(jpi, &next)) + break; + + jpi = &next; + } + + return status; +} + +/* + * Check whether jsonpath expression is immutable or not. + */ +bool +jspIsMutable(JsonPath *path, List *varnames, List *varexprs) +{ + JsonPathMutableContext cxt; + JsonPathItem jpi; + + cxt.varnames = varnames; + cxt.varexprs = varexprs; + cxt.current = jpdsNonDateTime; + cxt.lax = (path->header & JSONPATH_LAX) != 0; + cxt.mutable = false; + + jspInit(&jpi, path); + jspIsMutableWalker(&jpi, &cxt); + + return cxt.mutable; +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 078aaef539..3b596be9fa 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -86,12 +86,16 @@ typedef struct JsonBaseObjectInfo int id; } JsonBaseObjectInfo; +typedef int (*JsonPathVarCallback) (void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); + /* * Context of jsonpath execution. */ typedef struct JsonPathExecContext { - Jsonb *vars; /* variables to substitute into jsonpath */ + void *vars; /* variables to substitute into jsonpath */ + JsonPathVarCallback getVar; JsonbValue *root; /* for $ evaluation */ JsonbValue *current; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() @@ -173,7 +177,8 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); -static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars, +static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, + JsonPathVarCallback getVar, Jsonb *json, bool throwErrors, JsonValueList *result, bool useTz); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, @@ -225,7 +230,10 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value); static void getJsonPathVariable(JsonPathExecContext *cxt, - JsonPathItem *variable, Jsonb *vars, JsonbValue *value); + JsonPathItem *variable, JsonbValue *value); +static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, + int varNameLen, JsonbValue *val, + JsonbValue *baseObject); static int JsonbArraySize(JsonbValue *jb); static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p); @@ -283,7 +291,8 @@ jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz) silent = PG_GETARG_BOOL(3); } - res = executeJsonPath(jp, vars, jb, !silent, NULL, tz); + res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, NULL, tz); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -338,7 +347,8 @@ jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz) silent = PG_GETARG_BOOL(3); } - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -416,7 +426,8 @@ jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz) vars = PG_GETARG_JSONB_P_COPY(2); silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); funcctx->user_fctx = JsonValueListGetList(&found); @@ -463,7 +474,8 @@ jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); } @@ -494,7 +506,8 @@ jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found, tz); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found, tz); if (JsonValueListLength(&found) >= 1) PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); @@ -536,8 +549,9 @@ jsonb_path_query_first_tz(PG_FUNCTION_ARGS) * In other case it tries to find all the satisfied result items. */ static JsonPathExecResult -executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, - JsonValueList *result, bool useTz) +executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, + Jsonb *json, bool throwErrors, JsonValueList *result, + bool useTz) { JsonPathExecContext cxt; JsonPathExecResult res; @@ -549,22 +563,16 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, if (!JsonbExtractScalar(&json->root, &jbv)) JsonbInitBinary(&jbv, json); - if (vars && !JsonContainerIsObject(&vars->root)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"vars\" argument is not an object"), - errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."))); - } - cxt.vars = vars; + cxt.getVar = getVar; cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = &jbv; cxt.current = &jbv; cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; - cxt.lastGeneratedObjectId = vars ? 2 : 1; + /* 1 + number of base objects in vars */ + cxt.lastGeneratedObjectId = 1 + getVar(vars, NULL, 0, NULL, NULL); cxt.innermostArraySize = -1; cxt.throwErrors = throwErrors; cxt.useTz = useTz; @@ -2093,7 +2101,7 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, &value->val.string.len); break; case jpiVariable: - getJsonPathVariable(cxt, item, cxt->vars, value); + getJsonPathVariable(cxt, item, value); return; default: elog(ERROR, "unexpected jsonpath item type"); @@ -2105,42 +2113,63 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, */ static void getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, - Jsonb *vars, JsonbValue *value) + JsonbValue *value) { char *varName; int varNameLength; + JsonbValue baseObject; + int baseObjectId; + + Assert(variable->type == jpiVariable); + varName = jspGetString(variable, &varNameLength); + + if (!cxt->vars || + (baseObjectId = cxt->getVar(cxt->vars, varName, varNameLength, value, + &baseObject)) < 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find jsonpath variable \"%s\"", + pnstrdup(varName, varNameLength)))); + + if (baseObjectId > 0) + setBaseObject(cxt, &baseObject, baseObjectId); +} + +static int +getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, + JsonbValue *value, JsonbValue *baseObject) +{ + Jsonb *vars = varsJsonb; JsonbValue tmp; JsonbValue *v; - if (!vars) + if (!varName) { - value->type = jbvNull; - return; + if (vars && !JsonContainerIsObject(&vars->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"vars\" argument is not an object"), + errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."))); + } + + return vars ? 1 : 0; /* count of base objects */ } - Assert(variable->type == jpiVariable); - varName = jspGetString(variable, &varNameLength); tmp.type = jbvString; tmp.val.string.val = varName; tmp.val.string.len = varNameLength; v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp); - if (v) - { - *value = *v; - pfree(v); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find jsonpath variable \"%s\"", - pnstrdup(varName, varNameLength)))); - } + if (!v) + return -1; - JsonbInitBinary(&tmp, vars); - setBaseObject(cxt, &tmp, 1); + *value = *v; + pfree(v); + + JsonbInitBinary(baseObject, vars); + return 1; } /**************** Support functions for JsonPath execution *****************/ @@ -2797,3 +2826,244 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); } + +/********************Interface to pgsql's executor***************************/ + +bool +JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool *error) +{ + JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar, + DatumGetJsonbP(jb), !error, NULL, + true); + + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + *error = true; + + return res == jperOk; +} + +Datum +JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, + bool *error, List *vars) +{ + JsonbValue *first; + bool wrap; + JsonValueList found = {0}; + JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY; + int count; + + res = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error, + &found, true); + + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + { + *error = true; + *empty = false; + return (Datum) 0; + } + + count = JsonValueListLength(&found); + + first = count ? JsonValueListHead(&found) : NULL; + + if (!first) + wrap = false; + else if (wrapper == JSW_NONE) + wrap = false; + else if (wrapper == JSW_UNCONDITIONAL) + wrap = true; + else if (wrapper == JSW_CONDITIONAL) + wrap = count > 1 || + IsAJsonbScalar(first) || + (first->type == jbvBinary && + JsonContainerIsScalar(first->val.binary.data)); + else + { + elog(ERROR, "unrecognized json wrapper %d", wrapper); + wrap = false; + } + + if (wrap) + return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found))); + + if (count > 1) + { + if (error) + { + *error = true; + return (Datum) 0; + } + + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM), + errmsg("JSON path expression in JSON_QUERY should return " + "singleton item without wrapper"), + errhint("use WITH WRAPPER clause to wrap SQL/JSON item " + "sequence into array"))); + } + + if (first) + return JsonbPGetDatum(JsonbValueToJsonb(first)); + + *empty = true; + return PointerGetDatum(NULL); +} + +JsonbValue * +JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars) +{ + JsonbValue *res; + JsonValueList found = { 0 }; + JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + int count; + + jper = executeJsonPath(jp, vars, EvalJsonPathVar, DatumGetJsonbP(jb), !error, + &found, true); + + Assert(error || !jperIsError(jper)); + + if (error && jperIsError(jper)) + { + *error = true; + *empty = false; + return NULL; + } + + count = JsonValueListLength(&found); + + *empty = !count; + + if (*empty) + return NULL; + + if (count > 1) + { + if (error) + { + *error = true; + return NULL; + } + + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM), + errmsg("JSON path expression in JSON_VALUE should return " + "singleton scalar item"))); + } + + res = JsonValueListHead(&found); + + if (res->type == jbvBinary && + JsonContainerIsScalar(res->val.binary.data)) + JsonbExtractScalar(res->val.binary.data, res); + + if (!IsAJsonbScalar(res)) + { + if (error) + { + *error = true; + return NULL; + } + + ereport(ERROR, + (errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED), + errmsg("JSON path expression in JSON_VALUE should return " + "singleton scalar item"))); + } + + if (res->type == jbvNull) + return NULL; + + return res; +} + +static void +JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num) +{ + jbv->type = jbvNumeric; + jbv->val.numeric = DatumGetNumeric(num); +} + +void +JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonbValue *res) +{ + switch (typid) + { + case BOOLOID: + res->type = jbvBool; + res->val.boolean = DatumGetBool(val); + break; + case NUMERICOID: + JsonbValueInitNumericDatum(res, val); + break; + case INT2OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val)); + break; + case INT4OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val)); + break; + case INT8OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val)); + break; + case FLOAT4OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val)); + break; + case FLOAT8OID: + JsonbValueInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val)); + break; + case TEXTOID: + case VARCHAROID: + res->type = jbvString; + res->val.string.val = VARDATA_ANY(val); + res->val.string.len = VARSIZE_ANY_EXHDR(val); + break; + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + res->type = jbvDatetime; + res->val.datetime.value = val; + res->val.datetime.typid = typid; + res->val.datetime.typmod = typmod; + res->val.datetime.tz = 0; + break; + case JSONBOID: + { + JsonbValue *jbv = res; + Jsonb *jb = DatumGetJsonbP(val); + + if (JsonContainerIsScalar(&jb->root)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(&jb->root, jbv); + Assert(res); + } + else + JsonbInitBinary(jbv, jb); + break; + } + case JSONOID: + { + text *txt = DatumGetTextP(val); + char *str = text_to_cstring(txt); + Jsonb *jb = + DatumGetJsonbP(DirectFunctionCall1(jsonb_in, + CStringGetDatum(str))); + + pfree(str); + + JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res); + break; + } + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("only bool, numeric and text types could be " + "casted to supported jsonpath types."))); + } +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index ccd2835c22..b32f3b30c9 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -63,6 +63,7 @@ #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/hsearch.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/partcache.h" #include "utils/rel.h" @@ -453,6 +454,12 @@ static void get_coercion_expr(Node *arg, deparse_context *context, Node *parentNode); static void get_const_expr(Const *constval, deparse_context *context, int showtype); +static void get_json_constructor(JsonConstructorExpr *ctor, + deparse_context *context, bool showimplicit); +static void get_json_agg_constructor(JsonConstructorExpr *ctor, + deparse_context *context, + const char *funcname, + bool is_json_objectagg); static void get_const_collation(Const *constval, deparse_context *context); static void simple_quote_literal(StringInfo buf, const char *val); static void get_sublink_expr(SubLink *sublink, deparse_context *context); @@ -486,6 +493,8 @@ static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); static void get_reloptions(StringInfo buf, Datum reloptions); +static void get_json_path_spec(Node *path_spec, deparse_context *context, + bool showimplicit); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -6151,7 +6160,8 @@ get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno, bool need_paren = (PRETTY_PAREN(context) || IsA(expr, FuncExpr) || IsA(expr, Aggref) - || IsA(expr, WindowFunc)); + || IsA(expr, WindowFunc) + || IsA(expr, JsonConstructorExpr)); if (need_paren) appendStringInfoChar(context->buf, '('); @@ -7992,6 +8002,8 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: case T_WindowFunc: case T_FuncExpr: + case T_JsonConstructorExpr: + case T_JsonExpr: /* function-like: name(..) or name[..] */ return true; @@ -8085,6 +8097,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_NullTest: case T_BooleanTest: case T_DistinctExpr: + case T_JsonIsPredicate: switch (nodeTag(parentNode)) { case T_FuncExpr: @@ -8108,6 +8121,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: /* own parentheses */ case T_WindowFunc: /* own parentheses */ case T_CaseExpr: /* other separators */ + case T_JsonExpr: /* own parentheses */ return true; default: return false; @@ -8163,6 +8177,11 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) return false; } + case T_JsonValueExpr: + /* maybe simple, check args */ + return isSimpleNode((Node *) ((JsonValueExpr *) node)->raw_expr, + node, prettyFlags); + default: break; } @@ -8269,6 +8288,121 @@ get_rule_expr_paren(Node *node, deparse_context *context, } +/* + * get_json_path_spec - Parse back a JSON path specification + */ +static void +get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit) +{ + if (IsA(path_spec, Const)) + get_const_expr((Const *) path_spec, context, -1); + else + get_rule_expr(path_spec, context, showimplicit); +} + +/* + * get_json_format - Parse back a JsonFormat node + */ +static void +get_json_format(JsonFormat *format, StringInfo buf) +{ + if (format->format == JS_FORMAT_DEFAULT) + return; + + appendStringInfoString(buf, + format->format == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + if (format->encoding != JS_ENC_DEFAULT) + { + const char *encoding = + format->encoding == JS_ENC_UTF16 ? "UTF16" : + format->encoding == JS_ENC_UTF32 ? "UTF32" : "UTF8"; + + appendStringInfo(buf, " ENCODING %s", encoding); + } +} + +/* + * get_json_returning - Parse back a JsonReturning structure + */ +static void +get_json_returning(JsonReturning *returning, StringInfo buf, + bool json_format_by_default) +{ + if (!OidIsValid(returning->typid)) + return; + + appendStringInfo(buf, " RETURNING %s", + format_type_with_typemod(returning->typid, + returning->typmod)); + + if (!json_format_by_default || + returning->format->format != + (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) + get_json_format(returning->format, buf); +} + +static void +get_json_behavior(JsonBehavior *behavior, deparse_context *context, + const char *on) +{ + /* + * The order of array elements must correspond to the order of + * JsonBehaviorType members. + */ + const char *behavior_names[] = + { + " NULL", + " ERROR", + " EMPTY", + " TRUE", + " FALSE", + " UNKNOWN", + " EMPTY ARRAY", + " EMPTY OBJECT", + " DEFAULT " + }; + + if ((int) behavior->btype < 0 || behavior->btype >= lengthof(behavior_names)) + elog(ERROR, "invalid json behavior type: %d", behavior->btype); + + appendStringInfoString(context->buf, behavior_names[behavior->btype]); + + if (behavior->btype == JSON_BEHAVIOR_DEFAULT) + get_rule_expr(behavior->default_expr, context, false); + + appendStringInfo(context->buf, " ON %s", on); +} + +/* + * get_json_expr_options + * + * Parse back common options for JSON_QUERY, JSON_VALUE, JSON_EXISTS. + */ +static void +get_json_expr_options(JsonExpr *jsexpr, deparse_context *context, + JsonBehaviorType default_behavior) +{ + if (jsexpr->op == IS_JSON_QUERY) + { + if (jsexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(context->buf, " WITH CONDITIONAL WRAPPER"); + else if (jsexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(context->buf, " WITH UNCONDITIONAL WRAPPER"); + + if (jsexpr->omit_quotes) + appendStringInfo(context->buf, " OMIT QUOTES"); + } + + if (jsexpr->op != IS_JSON_EXISTS && + jsexpr->on_empty->btype != default_behavior) + get_json_behavior(jsexpr->on_empty, context, "EMPTY"); + + if (jsexpr->on_error->btype != default_behavior) + get_json_behavior(jsexpr->on_error, context, "ERROR"); +} + /* ---------- * get_rule_expr - Parse back an expression * @@ -9444,6 +9578,110 @@ get_rule_expr(Node *node, deparse_context *context, } break; + + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + get_rule_expr((Node *) jve->raw_expr, context, false); + get_json_format(jve->format, context->buf); + } + break; + + case T_JsonConstructorExpr: + get_json_constructor((JsonConstructorExpr *) node, context, false); + break; + + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, '('); + + get_rule_expr_paren(pred->expr, context, true, node); + + appendStringInfoString(context->buf, " IS JSON"); + + switch (pred->value_type) + { + case JS_TYPE_SCALAR: + appendStringInfoString(context->buf, " SCALAR"); + break; + case JS_TYPE_ARRAY: + appendStringInfoString(context->buf, " ARRAY"); + break; + case JS_TYPE_OBJECT: + appendStringInfoString(context->buf, " OBJECT"); + break; + default: + break; + } + + if (pred->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + if (!PRETTY_PAREN(context)) + appendStringInfoChar(context->buf, ')'); + } + break; + + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + switch (jexpr->op) + { + case IS_JSON_QUERY: + appendStringInfoString(buf, "JSON_QUERY("); + break; + case IS_JSON_VALUE: + appendStringInfoString(buf, "JSON_VALUE("); + break; + case IS_JSON_EXISTS: + appendStringInfoString(buf, "JSON_EXISTS("); + break; + } + + get_rule_expr(jexpr->formatted_expr, context, showimplicit); + + appendStringInfoString(buf, ", "); + + get_json_path_spec(jexpr->path_spec, context, showimplicit); + + if (jexpr->passing_values) + { + ListCell *lc1, *lc2; + bool needcomma = false; + + appendStringInfoString(buf, " PASSING "); + + forboth(lc1, jexpr->passing_names, + lc2, jexpr->passing_values) + { + if (needcomma) + appendStringInfoString(buf, ", "); + needcomma = true; + + get_rule_expr((Node *) lfirst(lc2), context, showimplicit); + appendStringInfo(buf, " AS %s", + ((String *) lfirst_node(String, lc1))->val); + } + } + + if (jexpr->op != IS_JSON_EXISTS || + jexpr->returning->typid != BOOLOID) + get_json_returning(jexpr->returning, context->buf, + jexpr->op == IS_JSON_QUERY); + + get_json_expr_options(jexpr, context, + jexpr->op == IS_JSON_EXISTS ? + JSON_BEHAVIOR_FALSE : JSON_BEHAVIOR_NULL); + + appendStringInfoString(buf, ")"); + } + break; + case T_List: { char *sep; @@ -9541,6 +9779,7 @@ looks_like_function(Node *node) case T_MinMaxExpr: case T_SQLValueFunction: case T_XmlExpr: + case T_JsonExpr: /* these are all accepted by func_expr_common_subexpr */ return true; default: @@ -9686,17 +9925,101 @@ get_func_expr(FuncExpr *expr, deparse_context *context, appendStringInfoChar(buf, ')'); } +static void +get_json_constructor_options(JsonConstructorExpr *ctor, StringInfo buf) +{ + if (ctor->absent_on_null) + { + if (ctor->type == JSCTOR_JSON_OBJECT || + ctor->type == JSCTOR_JSON_OBJECTAGG) + appendStringInfoString(buf, " ABSENT ON NULL"); + } + else + { + if (ctor->type == JSCTOR_JSON_ARRAY || + ctor->type == JSCTOR_JSON_ARRAYAGG) + appendStringInfoString(buf, " NULL ON NULL"); + } + + if (ctor->unique) + appendStringInfoString(buf, " WITH UNIQUE KEYS"); + + if (!((ctor->type == JSCTOR_JSON_PARSE || + ctor->type == JSCTOR_JSON_SCALAR) && + ctor->returning->typid == SQLJSON_TYPE_OID())) + get_json_returning(ctor->returning, buf, true); +} + +static void +get_json_constructor(JsonConstructorExpr *ctor, deparse_context *context, + bool showimplicit) +{ + StringInfo buf = context->buf; + const char *funcname; + int nargs; + ListCell *lc; + + switch (ctor->type) + { + case JSCTOR_JSON_PARSE: + funcname = "JSON"; + break; + case JSCTOR_JSON_SCALAR: + funcname = "JSON_SCALAR"; + break; + case JSCTOR_JSON_SERIALIZE: + funcname = "JSON_SERIALIZE"; + break; + case JSCTOR_JSON_OBJECT: + funcname = "JSON_OBJECT"; + break; + case JSCTOR_JSON_ARRAY: + funcname = "JSON_ARRAY"; + break; + case JSCTOR_JSON_OBJECTAGG: + return get_json_agg_constructor(ctor, context, "JSON_OBJECTAGG", true); + case JSCTOR_JSON_ARRAYAGG: + return get_json_agg_constructor(ctor, context, "JSON_ARRAYAGG", false); + default: + elog(ERROR, "invalid JsonConstructorExprType %d", ctor->type); + } + + appendStringInfo(buf, "%s(", funcname); + + nargs = 0; + foreach(lc, ctor->args) + { + if (nargs > 0) + { + const char *sep = ctor->type == JSCTOR_JSON_OBJECT && + (nargs % 2) != 0 ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + + get_rule_expr((Node *) lfirst(lc), context, true); + + nargs++; + } + + get_json_constructor_options(ctor, buf); + + appendStringInfo(buf, ")"); +} + + /* - * get_agg_expr - Parse back an Aggref node + * get_agg_expr_helper - Parse back an Aggref node */ static void -get_agg_expr(Aggref *aggref, deparse_context *context, - Aggref *original_aggref) +get_agg_expr_helper(Aggref *aggref, deparse_context *context, + Aggref *original_aggref, const char *funcname, + const char *options, bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; int nargs; - bool use_variadic; + bool use_variadic = false; /* * For a combining aggregate, we look up and deparse the corresponding @@ -9726,13 +10049,14 @@ get_agg_expr(Aggref *aggref, deparse_context *context, /* Extract the argument types as seen by the parser */ nargs = get_aggregate_argtypes(aggref, argtypes); + if (!funcname) + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + /* Print the aggregate name, schema-qualified if needed */ - appendStringInfo(buf, "%s(%s", - generate_function_name(aggref->aggfnoid, nargs, - NIL, argtypes, - aggref->aggvariadic, - &use_variadic, - context->special_exprkind), + appendStringInfo(buf, "%s(%s", funcname, (aggref->aggdistinct != NIL) ? "DISTINCT " : ""); if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) @@ -9768,7 +10092,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context, if (tle->resjunk) continue; if (i++ > 0) - appendStringInfoString(buf, ", "); + { + if (is_json_objectagg) + { + if (i > 2) + break; /* skip ABSENT ON NULL and WITH UNIQUE args */ + + appendStringInfoString(buf, " : "); + } + else + appendStringInfoString(buf, ", "); + } if (use_variadic && i == nargs) appendStringInfoString(buf, "VARIADIC "); get_rule_expr(arg, context, true); @@ -9782,6 +10116,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context, } } + if (options) + appendStringInfoString(buf, options); + if (aggref->aggfilter != NULL) { appendStringInfoString(buf, ") FILTER (WHERE "); @@ -9791,6 +10128,16 @@ get_agg_expr(Aggref *aggref, deparse_context *context, appendStringInfoChar(buf, ')'); } +/* + * get_agg_expr - Parse back an Aggref node + */ +static void +get_agg_expr(Aggref *aggref, deparse_context *context, Aggref *original_aggref) +{ + return get_agg_expr_helper(aggref, context, original_aggref, NULL, NULL, + false); +} + /* * This is a helper function for get_agg_expr(). It's used when we deparse * a combining Aggref; resolve_special_varno locates the corresponding partial @@ -9810,10 +10157,12 @@ get_agg_combine_expr(Node *node, deparse_context *context, void *callback_arg) } /* - * get_windowfunc_expr - Parse back a WindowFunc node + * get_windowfunc_expr_helper - Parse back a WindowFunc node */ static void -get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context, + const char *funcname, const char *options, + bool is_json_objectagg) { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; @@ -9837,16 +10186,30 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) nargs++; } - appendStringInfo(buf, "%s(", - generate_function_name(wfunc->winfnoid, nargs, - argnames, argtypes, - false, NULL, - context->special_exprkind)); + if (!funcname) + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + + appendStringInfo(buf, "%s(", funcname); + /* winstar can be set only in zero-argument aggregates */ if (wfunc->winstar) appendStringInfoChar(buf, '*'); else - get_rule_expr((Node *) wfunc->args, context, true); + { + if (is_json_objectagg) + { + get_rule_expr((Node *) linitial(wfunc->args), context, false); + appendStringInfoString(buf, " : "); + get_rule_expr((Node *) lsecond(wfunc->args), context, false); + } + else + get_rule_expr((Node *) wfunc->args, context, true); + } + + if (options) + appendStringInfoString(buf, options); if (wfunc->aggfilter != NULL) { @@ -9883,6 +10246,15 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) } } +/* + * get_windowfunc_expr - Parse back a WindowFunc node + */ +static void +get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) +{ + return get_windowfunc_expr_helper(wfunc, context, NULL, NULL, false); +} + /* * get_func_sql_syntax - Parse back a SQL-syntax function call * @@ -10123,6 +10495,31 @@ get_func_sql_syntax(FuncExpr *expr, deparse_context *context) return false; } +/* + * get_json_agg_constructor - Parse back an aggregate JsonConstructorExpr node + */ +static void +get_json_agg_constructor(JsonConstructorExpr *ctor, deparse_context *context, + const char *funcname, bool is_json_objectagg) +{ + StringInfoData options; + + initStringInfo(&options); + get_json_constructor_options(ctor, &options); + + if (IsA(ctor->func, Aggref)) + return get_agg_expr_helper((Aggref *) ctor->func, context, + (Aggref *) ctor->func, + funcname, options.data, is_json_objectagg); + else if (IsA(ctor->func, WindowFunc)) + return get_windowfunc_expr_helper((WindowFunc *) ctor->func, context, + funcname, options.data, + is_json_objectagg); + else + elog(ERROR, "invalid JsonConstructorExpr underlying node type: %d", + nodeTag(ctor->func)); +} + /* ---------- * get_coercion_expr * diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 23236fa4c3..8975065e1e 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -95,6 +95,7 @@ #include "utils/bytea.h" #include "utils/float.h" #include "utils/guc_tables.h" +#include "utils/jsonb.h" #include "utils/memutils.h" #include "utils/pg_locale.h" #include "utils/pg_lsn.h" @@ -556,6 +557,12 @@ static const struct config_enum_entry wal_compression_options[] = { {NULL, 0, false} }; +const struct config_enum_entry sql_json_type_info[] = { + {"json", SQLJSON_TYPE_JSON, false}, + {"jsonb", SQLJSON_TYPE_JSONB, false}, + {NULL, 0, false} +}; + /* * Options for enum values stored in other modules */ @@ -4982,6 +4989,18 @@ static struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL }, + { + {"sql_json", PGC_USERSET, COMPAT_OPTIONS_CLIENT, + gettext_noop("Sets what PostgreSQL type to use as an implementaion of SQL JSON type."), + gettext_noop("When turned on, jsonb type is mapped to SQL JSON type, " + "json type is mapped to JSON TEXT type.") + }, + &sql_json_type, + SQLJSON_TYPE_JSON, + sql_json_type_info, + NULL, NULL, NULL + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, 0, NULL, NULL, NULL, NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 3fe9a53cb3..3292846f89 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -762,6 +762,7 @@ # - Other Platforms and Clients - #transform_null_equals = off +#sql_json = json # jsonb #------------------------------------------------------------------------------ diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c index 9f2cd1f127..fd632d712e 100644 --- a/src/backend/utils/misc/queryjumble.c +++ b/src/backend/utils/misc/queryjumble.c @@ -737,6 +737,75 @@ JumbleExpr(JumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) conf->exclRelTlist); } break; + case T_JsonFormat: + { + JsonFormat *format = (JsonFormat *) node; + + APP_JUMB(format->type); + APP_JUMB(format->encoding); + } + break; + case T_JsonReturning: + { + JsonReturning *returning = (JsonReturning *) node; + + JumbleExpr(jstate, (Node *) returning->format); + APP_JUMB(returning->typid); + APP_JUMB(returning->typmod); + } + break; + case T_JsonValueExpr: + { + JsonValueExpr *expr = (JsonValueExpr *) node; + + JumbleExpr(jstate, (Node *) expr->raw_expr); + JumbleExpr(jstate, (Node *) expr->formatted_expr); + JumbleExpr(jstate, (Node *) expr->format); + } + break; + case T_JsonConstructorExpr: + { + JsonConstructorExpr *ctor = (JsonConstructorExpr *) node; + + JumbleExpr(jstate, (Node *) ctor->func); + JumbleExpr(jstate, (Node *) ctor->coercion); + JumbleExpr(jstate, (Node *) ctor->returning); + APP_JUMB(ctor->type); + APP_JUMB(ctor->unique); + APP_JUMB(ctor->absent_on_null); + } + break; + case T_JsonIsPredicate: + { + JsonIsPredicate *pred = (JsonIsPredicate *) node; + + JumbleExpr(jstate, (Node *) pred->expr); + JumbleExpr(jstate, (Node *) pred->format); + APP_JUMB(pred->unique_keys); + APP_JUMB(pred->value_type); + } + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + APP_JUMB(jexpr->op); + JumbleExpr(jstate, jexpr->formatted_expr); + JumbleExpr(jstate, jexpr->path_spec); + foreach(temp, jexpr->passing_names) + { + APP_JUMB_STRING(castNode(String, temp)->val); + } + JumbleExpr(jstate, (Node *) jexpr->passing_values); + if (jexpr->on_empty) + { + APP_JUMB(jexpr->on_empty->btype); + JumbleExpr(jstate, jexpr->on_empty->default_expr); + } + APP_JUMB(jexpr->on_error->btype); + JumbleExpr(jstate, jexpr->on_error->default_expr); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index fc6d3bfd94..aaf6e20e0b 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -561,14 +561,36 @@ # json { aggfnoid => 'json_agg', aggtransfn => 'json_agg_transfn', aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_agg_strict', aggtransfn => 'json_agg_strict_transfn', + aggfinalfn => 'json_agg_finalfn', aggtranstype => 'internal' }, { aggfnoid => 'json_object_agg', aggtransfn => 'json_object_agg_transfn', aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_unique', + aggtransfn => 'json_object_agg_unique_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_strict', + aggtransfn => 'json_object_agg_strict_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'json_object_agg_unique_strict', + aggtransfn => 'json_object_agg_unique_strict_transfn', + aggfinalfn => 'json_object_agg_finalfn', aggtranstype => 'internal' }, # jsonb { aggfnoid => 'jsonb_agg', aggtransfn => 'jsonb_agg_transfn', aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_agg_strict', aggtransfn => 'jsonb_agg_strict_transfn', + aggfinalfn => 'jsonb_agg_finalfn', aggtranstype => 'internal' }, { aggfnoid => 'jsonb_object_agg', aggtransfn => 'jsonb_object_agg_transfn', aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_unique', + aggtransfn => 'jsonb_object_agg_unique_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_strict', + aggtransfn => 'jsonb_object_agg_strict_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, +{ aggfnoid => 'jsonb_object_agg_unique_strict', + aggtransfn => 'jsonb_object_agg_unique_strict_transfn', + aggfinalfn => 'jsonb_object_agg_finalfn', aggtranstype => 'internal' }, # ordered-set and hypothetical-set aggregates { aggfnoid => 'percentile_disc(float8,anyelement)', aggkind => 'o', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index d068d6532e..50a773009f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8722,6 +8722,10 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, +{ oid => '8173', descr => 'json aggregate transition function', + proname => 'json_agg_strict_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal anyelement', + prosrc => 'json_agg_strict_transfn' }, { oid => '3174', descr => 'json aggregate final function', proname => 'json_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', prosrc => 'json_agg_finalfn' }, @@ -8729,10 +8733,26 @@ proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '8174', descr => 'aggregate input into json', + proname => 'json_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', + prosrc => 'aggregate_dummy' }, { oid => '3180', descr => 'json object aggregate transition function', proname => 'json_object_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', prosrc => 'json_object_agg_transfn' }, +{ oid => '8175', descr => 'json object aggregate transition function', + proname => 'json_object_agg_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_strict_transfn' }, +{ oid => '8176', descr => 'json object aggregate transition function', + proname => 'json_object_agg_unique_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_unique_transfn' }, +{ oid => '8177', descr => 'json object aggregate transition function', + proname => 'json_object_agg_unique_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', + prosrc => 'json_object_agg_unique_strict_transfn' }, { oid => '3196', descr => 'json object aggregate final function', proname => 'json_object_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', @@ -8741,6 +8761,19 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '8178', descr => 'aggregate non-NULL input into a json object', + proname => 'json_object_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8179', descr => 'aggregate input into a json object with unique keys', + proname => 'json_object_agg_unique', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8180', + descr => 'aggregate non-NULL input into a json object with unique keys', + proname => 'json_object_agg_unique_strict', prokind => 'a', + proisstrict => 'f', provolatile => 's', prorettype => 'json', + proargtypes => 'any any', prosrc => 'aggregate_dummy' }, { oid => '3198', descr => 'build a json array from any inputs', proname => 'json_build_array', provariadic => 'any', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any', @@ -9613,6 +9646,10 @@ proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'jsonb_agg_transfn' }, +{ oid => '8181', descr => 'jsonb aggregate transition function', + proname => 'jsonb_agg_strict_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal anyelement', + prosrc => 'jsonb_agg_strict_transfn' }, { oid => '3266', descr => 'jsonb aggregate final function', proname => 'jsonb_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -9621,10 +9658,29 @@ proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '8182', descr => 'aggregate input into jsonb skipping nulls', + proname => 'jsonb_agg_strict', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', + prosrc => 'aggregate_dummy' }, { oid => '3268', descr => 'jsonb object aggregate transition function', proname => 'jsonb_object_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', prosrc => 'jsonb_object_agg_transfn' }, +{ oid => '8183', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_strict_transfn' }, +{ oid => '8184', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_unique_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_unique_transfn' }, +{ oid => '8185', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_object_agg_unique_strict_transfn', proisstrict => 'f', + provolatile => 's', prorettype => 'internal', + proargtypes => 'internal any any', + prosrc => 'jsonb_object_agg_unique_strict_transfn' }, { oid => '3269', descr => 'jsonb object aggregate final function', proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -9633,6 +9689,20 @@ proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '8186', descr => 'aggregate non-NULL inputs into jsonb object', + proname => 'jsonb_object_agg_strict', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8187', + descr => 'aggregate inputs into jsonb object checking key uniqueness', + proname => 'jsonb_object_agg_unique', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, +{ oid => '8188', + descr => 'aggregate non-NULL inputs into jsonb object checking key uniqueness', + proname => 'jsonb_object_agg_unique_strict', prokind => 'a', + proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', + prosrc => 'aggregate_dummy' }, { oid => '3271', descr => 'build a jsonb array from any inputs', proname => 'jsonb_build_array', provariadic => 'any', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'any', diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 6a24341faa..00916724d9 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -21,6 +21,7 @@ struct ExprEvalStep; struct SubscriptingRefState; struct ScalarArrayOpExprHashTable; +struct JsonbValue; /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ /* expression's interpreter has been initialized */ @@ -239,6 +240,9 @@ typedef enum ExprEvalOp EEOP_GROUPING_FUNC, EEOP_WINDOW_FUNC, EEOP_SUBPLAN, + EEOP_JSON_CONSTRUCTOR, + EEOP_IS_JSON, + EEOP_JSONEXPR, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -668,6 +672,72 @@ typedef struct ExprEvalStep int transno; int setoff; } agg_trans; + + /* for EEOP_JSON_CONSTRUCTOR */ + struct + { + JsonConstructorExpr *constructor; + Datum *arg_values; + bool *arg_nulls; + Oid *arg_types; + struct + { + int category; + Oid outfuncid; + } *arg_type_cache; /* cache for datum_to_json[b]() */ + int nargs; + } json_constructor; + + /* for EEOP_IS_JSON */ + struct + { + JsonIsPredicate *pred; /* original expression node */ + } is_json; + + /* for EEOP_JSONEXPR */ + struct + { + JsonExpr *jsexpr; /* original expression node */ + + struct + { + FmgrInfo func; /* typinput function for output type */ + Oid typioparam; + } input; /* I/O info for output type */ + + NullableDatum + *formatted_expr, /* formatted context item value */ + *res_expr, /* result item */ + *coercion_expr, /* input for JSON item coercion */ + *pathspec; /* path specification value */ + + ExprState *result_expr; /* coerced to output type */ + ExprState *default_on_empty; /* ON EMPTY DEFAULT expression */ + ExprState *default_on_error; /* ON ERROR DEFAULT expression */ + List *args; /* passing arguments */ + + void *cache; /* cache for json_populate_type() */ + + struct JsonCoercionsState + { + struct JsonCoercionState + { + JsonCoercion *coercion; /* coercion expression */ + ExprState *estate; /* coercion expression state */ + } null, + string, + numeric, + boolean, + date, + time, + timetz, + timestamp, + timestamptz, + composite; + } coercions; /* states for coercion from SQL/JSON item + * types directly to the output type */ + } jsonexpr; + } d; } ExprEvalStep; @@ -762,6 +832,7 @@ extern void ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op); extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op); extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op); +extern void ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op); extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext); @@ -769,6 +840,16 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); +extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pjcstate); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, + struct JsonCoercionsState *); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup, ExprContext *aggcontext); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 3dc03c913e..09e3cde548 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -263,6 +263,8 @@ ExecProcNode(PlanState *node) */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); extern ExprState *ExecInitExprWithParams(Expr *node, ParamListInfo ext_params); +extern ExprState *ExecInitExprWithCaseValue(Expr *node, PlanState *parent, + Datum *caseval, bool *casenull); extern ExprState *ExecInitQual(List *qual, PlanState *parent); extern ExprState *ExecInitCheck(List *qual, PlanState *parent); extern List *ExecInitExprList(List *nodes, PlanState *parent); diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 48a7ebfe45..3f55d4b7fb 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -106,4 +106,14 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols); +extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding, + int location); +extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format); +extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr); +extern Node *makeJsonKeyValue(Node *key, Node *value); +extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format, + JsonValueType vtype, bool unique_keys, + int location); +extern JsonEncoding makeJsonEncoding(char *name); + #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index b3ee4194d3..f6e71974b8 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -201,6 +201,16 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, T_IntoClause, + T_JsonFormat, + T_JsonReturning, + T_JsonValueExpr, + T_JsonParseExpr, + T_JsonScalarExpr, + T_JsonSerializeExpr, + T_JsonConstructorExpr, + T_JsonExpr, + T_JsonCoercion, + T_JsonItemCoercions, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -488,6 +498,20 @@ typedef enum NodeTag T_PartitionCmd, T_VacuumRelation, T_PublicationTable, + T_JsonObjectConstructor, + T_JsonArrayConstructor, + T_JsonArrayQueryConstructor, + T_JsonAggConstructor, + T_JsonObjectAgg, + T_JsonArrayAgg, + T_JsonFuncExpr, + T_JsonIsPredicate, + T_JsonExistsPredicate, + T_JsonCommon, + T_JsonArgument, + T_JsonKeyValue, + T_JsonBehavior, + T_JsonOutput, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 45e4f2a16e..1007128c57 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1550,6 +1550,209 @@ typedef struct TriggerTransition bool isTable; } TriggerTransition; +/* Nodes for SQL/JSON support */ + +/* + * JsonQuotes - + * representation of [KEEP|OMIT] QUOTES clause for JSON_QUERY() + */ +typedef enum JsonQuotes +{ + JS_QUOTES_UNSPEC, /* unspecified */ + JS_QUOTES_KEEP, /* KEEP QUOTES */ + JS_QUOTES_OMIT /* OMIT QUOTES */ +} JsonQuotes; + +/* + * JsonPathSpec - + * representation of JSON path constant + */ +typedef char *JsonPathSpec; + +/* + * JsonOutput - + * representation of JSON output clause (RETURNING type [FORMAT format]) + */ +typedef struct JsonOutput +{ + NodeTag type; + TypeName *typeName; /* RETURNING type name, if specified */ + JsonReturning *returning; /* RETURNING FORMAT clause and type Oids */ +} JsonOutput; + +/* + * JsonArgument - + * representation of argument from JSON PASSING clause + */ +typedef struct JsonArgument +{ + NodeTag type; + JsonValueExpr *val; /* argument value expression */ + char *name; /* argument name */ +} JsonArgument; + +/* + * JsonCommon - + * representation of common syntax of functions using JSON path + */ +typedef struct JsonCommon +{ + NodeTag type; + JsonValueExpr *expr; /* context item expression */ + Node *pathspec; /* JSON path specification expression */ + char *pathname; /* path name, if any */ + List *passing; /* list of PASSING clause arguments, if any */ + int location; /* token location, or -1 if unknown */ +} JsonCommon; + +/* + * JsonFuncExpr - + * untransformed representation of JSON function expressions + */ +typedef struct JsonFuncExpr +{ + NodeTag type; + JsonExprOp op; /* expression type */ + JsonCommon *common; /* common syntax */ + JsonOutput *output; /* output clause, if specified */ + JsonBehavior *on_empty; /* ON EMPTY behavior, if specified */ + JsonBehavior *on_error; /* ON ERROR behavior, if specified */ + JsonWrapper wrapper; /* array wrapper behavior (JSON_QUERY only) */ + bool omit_quotes; /* omit or keep quotes? (JSON_QUERY only) */ + int location; /* token location, or -1 if unknown */ +} JsonFuncExpr; + +/* + * JsonKeyValue - + * untransformed representation of JSON object key-value pair for + * JSON_OBJECT() and JSON_OBJECTAGG() + */ +typedef struct JsonKeyValue +{ + NodeTag type; + Expr *key; /* key expression */ + JsonValueExpr *value; /* JSON value expression */ +} JsonKeyValue; + +/* + * JsonParseExpr - + * untransformed representation of JSON() + */ +typedef struct JsonParseExpr +{ + NodeTag type; + JsonValueExpr *expr; /* string expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool unique_keys; /* WITH UNIQUE KEYS? */ + int location; /* token location, or -1 if unknown */ +} JsonParseExpr; + +/* + * JsonScalarExpr - + * untransformed representation of JSON_SCALAR() + */ +typedef struct JsonScalarExpr +{ + NodeTag type; + Expr *expr; /* scalar expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + int location; /* token location, or -1 if unknown */ +} JsonScalarExpr; + +/* + * JsonSerializeExpr - + * untransformed representation of JSON_SERIALIZE() function + */ +typedef struct JsonSerializeExpr +{ + NodeTag type; + JsonValueExpr *expr; /* json value expression */ + JsonOutput *output; /* RETURNING clause, if specified */ + int location; /* token location, or -1 if unknown */ +} JsonSerializeExpr; + +/* + * JsonObjectConstructor - + * untransformed representation of JSON_OBJECT() constructor + */ +typedef struct JsonObjectConstructor +{ + NodeTag type; + List *exprs; /* list of JsonKeyValue pairs */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ + int location; /* token location, or -1 if unknown */ +} JsonObjectConstructor; + +/* + * JsonArrayConstructor - + * untransformed representation of JSON_ARRAY(element,...) constructor + */ +typedef struct JsonArrayConstructor +{ + NodeTag type; + List *exprs; /* list of JsonValueExpr elements */ + JsonOutput *output; /* RETURNING clause, if specified */ + bool absent_on_null; /* skip NULL elements? */ + int location; /* token location, or -1 if unknown */ +} JsonArrayConstructor; + +/* + * JsonArrayQueryConstructor - + * untransformed representation of JSON_ARRAY(subquery) constructor + */ +typedef struct JsonArrayQueryConstructor +{ + NodeTag type; + Node *query; /* subquery */ + JsonOutput *output; /* RETURNING clause, if specified */ + JsonFormat *format; /* FORMAT clause for subquery, if specified */ + bool absent_on_null; /* skip NULL elements? */ + int location; /* token location, or -1 if unknown */ +} JsonArrayQueryConstructor; + +/* + * JsonAggConstructor - + * common fields of untransformed representation of + * JSON_ARRAYAGG() and JSON_OBJECTAGG() + */ +typedef struct JsonAggConstructor +{ + NodeTag type; + JsonOutput *output; /* RETURNING clause, if any */ + Node *agg_filter; /* FILTER clause, if any */ + List *agg_order; /* ORDER BY clause, if any */ + struct WindowDef *over; /* OVER clause, if any */ + int location; /* token location, or -1 if unknown */ +} JsonAggConstructor; + +/* + * JsonObjectAgg - + * untransformed representation of JSON_OBJECTAGG() + */ +typedef struct JsonObjectAgg +{ + NodeTag type; + JsonAggConstructor *constructor; /* common fields */ + JsonKeyValue *arg; /* object key-value pair */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ +} JsonObjectAgg; + +/* + * JsonArrayAgg - + * untransformed representation of JSON_ARRRAYAGG() + */ +typedef struct JsonArrayAgg +{ + NodeTag type; + JsonAggConstructor *constructor; /* common fields */ + JsonValueExpr *arg; /* array element expression */ + bool absent_on_null; /* skip NULL elements? */ +} JsonArrayAgg; + + /***************************************************************************** * Raw Grammar Output Statements *****************************************************************************/ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7b125904b4..5e5f7fed5e 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1235,6 +1235,228 @@ typedef struct XmlExpr int location; /* token location, or -1 if unknown */ } XmlExpr; +/* + * JsonExprOp - + * enumeration of JSON functions using JSON path + */ +typedef enum JsonExprOp +{ + IS_JSON_VALUE, /* JSON_VALUE() */ + IS_JSON_QUERY, /* JSON_QUERY() */ + IS_JSON_EXISTS /* JSON_EXISTS() */ +} JsonExprOp; + +/* + * JsonEncoding - + * representation of JSON ENCODING clause + */ +typedef enum JsonEncoding +{ + JS_ENC_DEFAULT, /* unspecified */ + JS_ENC_UTF8, + JS_ENC_UTF16, + JS_ENC_UTF32, +} JsonEncoding; + +/* + * JsonFormatType - + * enumeration of JSON formats used in JSON FORMAT clause + */ +typedef enum JsonFormatType +{ + JS_FORMAT_DEFAULT, /* unspecified */ + JS_FORMAT_JSON, /* FORMAT JSON [ENCODING ...] */ + JS_FORMAT_JSONB /* implicit internal format for RETURNING jsonb */ +} JsonFormatType; + +/* + * JsonBehaviorType - + * enumeration of behavior types used in JSON ON ... BEHAVIOR clause + * + * If enum members are reordered, get_json_behavior() from ruleutils.c + * must be updated accordingly. + */ +typedef enum JsonBehaviorType +{ + JSON_BEHAVIOR_NULL = 0, + JSON_BEHAVIOR_ERROR, + JSON_BEHAVIOR_EMPTY, + JSON_BEHAVIOR_TRUE, + JSON_BEHAVIOR_FALSE, + JSON_BEHAVIOR_UNKNOWN, + JSON_BEHAVIOR_EMPTY_ARRAY, + JSON_BEHAVIOR_EMPTY_OBJECT, + JSON_BEHAVIOR_DEFAULT +} JsonBehaviorType; + +/* + * JsonWrapper - + * representation of WRAPPER clause for JSON_QUERY() + */ +typedef enum JsonWrapper +{ + JSW_NONE, + JSW_CONDITIONAL, + JSW_UNCONDITIONAL, +} JsonWrapper; + +/* + * JsonFormat - + * representation of JSON FORMAT clause + */ +typedef struct JsonFormat +{ + NodeTag type; + JsonFormatType format; /* format type */ + JsonEncoding encoding; /* JSON encoding */ + int location; /* token location, or -1 if unknown */ +} JsonFormat; + +/* + * JsonReturning - + * transformed representation of JSON RETURNING clause + */ +typedef struct JsonReturning +{ + NodeTag type; + JsonFormat *format; /* output JSON format */ + Oid typid; /* target type Oid */ + int32 typmod; /* target type modifier */ +} JsonReturning; + +/* + * JsonValueExpr - + * representation of JSON value expression (expr [FORMAT json_format]) + */ +typedef struct JsonValueExpr +{ + NodeTag type; + Expr *raw_expr; /* raw expression */ + Expr *formatted_expr; /* formatted expression or NULL */ + JsonFormat *format; /* FORMAT clause, if specified */ +} JsonValueExpr; + +typedef enum JsonConstructorType +{ + JSCTOR_JSON_OBJECT = 1, + JSCTOR_JSON_ARRAY = 2, + JSCTOR_JSON_OBJECTAGG = 3, + JSCTOR_JSON_ARRAYAGG = 4, + JSCTOR_JSON_SCALAR = 5, + JSCTOR_JSON_SERIALIZE = 6, + JSCTOR_JSON_PARSE = 7 +} JsonConstructorType; + +/* + * JsonConstructorExpr - + * wrapper over FuncExpr/Aggref/WindowFunc for SQL/JSON constructors + */ +typedef struct JsonConstructorExpr +{ + Expr xpr; + JsonConstructorType type; /* constructor type */ + List *args; + Expr *func; /* underlying json[b]_xxx() function call */ + Expr *coercion; /* coercion to RETURNING type */ + JsonReturning *returning; /* RETURNING clause */ + bool absent_on_null; /* ABSENT ON NULL? */ + bool unique; /* WITH UNIQUE KEYS? (JSON_OBJECT[AGG] only) */ + int location; +} JsonConstructorExpr; + +/* + * JsonValueType - + * representation of JSON item type in IS JSON predicate + */ +typedef enum JsonValueType +{ + JS_TYPE_ANY, /* IS JSON [VALUE] */ + JS_TYPE_OBJECT, /* IS JSON OBJECT */ + JS_TYPE_ARRAY, /* IS JSON ARRAY*/ + JS_TYPE_SCALAR /* IS JSON SCALAR */ +} JsonValueType; + +/* + * JsonIsPredicate - + * untransformed representation of IS JSON predicate + */ +typedef struct JsonIsPredicate +{ + NodeTag type; + Node *expr; /* untransformed expression */ + JsonFormat *format; /* FORMAT clause, if specified */ + JsonValueType value_type; /* JSON item type */ + bool unique_keys; /* check key uniqueness? */ + int location; /* token location, or -1 if unknown */ +} JsonIsPredicate; + +/* + * JsonBehavior - + * representation of JSON ON ... BEHAVIOR clause + */ +typedef struct JsonBehavior +{ + NodeTag type; + JsonBehaviorType btype; /* behavior type */ + Node *default_expr; /* default expression, if any */ +} JsonBehavior; + +/* + * JsonCoercion - + * coercion from SQL/JSON item types to SQL types + */ +typedef struct JsonCoercion +{ + NodeTag type; + Node *expr; /* resulting expression coerced to target type */ + bool via_populate; /* coerce result using json_populate_type()? */ + bool via_io; /* coerce result using type input function? */ + Oid collation; /* collation for coercion via I/O or populate */ +} JsonCoercion; + +/* + * JsonItemCoercions - + * expressions for coercion from SQL/JSON item types directly to the + * output SQL type + */ +typedef struct JsonItemCoercions +{ + NodeTag type; + JsonCoercion *null; + JsonCoercion *string; + JsonCoercion *numeric; + JsonCoercion *boolean; + JsonCoercion *date; + JsonCoercion *time; + JsonCoercion *timetz; + JsonCoercion *timestamp; + JsonCoercion *timestamptz; + JsonCoercion *composite; /* arrays and objects */ +} JsonItemCoercions; + +/* + * JsonExpr - + * transformed representation of JSON_VALUE(), JSON_QUERY(), JSON_EXISTS() + */ +typedef struct JsonExpr +{ + Expr xpr; + JsonExprOp op; /* json function ID */ + Node *formatted_expr; /* formatted context item expression */ + JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */ + JsonFormat *format; /* context item format (JSON/JSONB) */ + Node *path_spec; /* JSON path specification expression */ + List *passing_names; /* PASSING argument names */ + List *passing_values; /* PASSING argument values */ + JsonReturning *returning; /* RETURNING clause type/format info */ + JsonBehavior *on_empty; /* ON EMPTY behavior */ + JsonBehavior *on_error; /* ON ERROR behavior */ + JsonItemCoercions *coercions; /* coercions for JSON_VALUE */ + JsonWrapper wrapper; /* WRAPPER for JSON_QUERY */ + bool omit_quotes; /* KEEP/OMIT QUOTES for JSON_QUERY */ + int location; /* token location, or -1 if unknown */ +} JsonExpr; + /* ---------------- * NullTest * diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index f836acf876..458f55fbec 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -26,6 +26,7 @@ /* name, value, category, is-bare-label */ PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD, BARE_LABEL) @@ -92,6 +93,7 @@ PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("compression", COMPRESSION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD, BARE_LABEL) @@ -146,11 +148,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("end", END_P, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD, AS_LABEL) @@ -175,6 +179,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("for", FOR, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("format", FORMAT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("from", FROM, RESERVED_KEYWORD, AS_LABEL) @@ -227,7 +232,19 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json", JSON, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_scalar", JSON_SCALAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_serialize", JSON_SERIALIZE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD, BARE_LABEL) @@ -289,6 +306,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("on", ON, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("only", ONLY, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD, BARE_LABEL) @@ -330,6 +348,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("read", READ, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("real", REAL, COL_NAME_KEYWORD, BARE_LABEL) @@ -364,6 +383,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL) @@ -399,6 +419,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("string", STRING, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD, BARE_LABEL) @@ -433,6 +454,7 @@ PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("uescape", UESCAPE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD, BARE_LABEL) +PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("union", UNION, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD, BARE_LABEL) diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 93f979f320..c20a6a6d5f 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -17,6 +17,9 @@ #ifndef _FORMATTING_H_ #define _FORMATTING_H_ +#define DCH_DATED 0x01 +#define DCH_TIMED 0x02 +#define DCH_ZONED 0x04 extern char *str_tolower(const char *buff, size_t nbytes, Oid collid); extern char *str_toupper(const char *buff, size_t nbytes, Oid collid); @@ -29,5 +32,6 @@ extern char *asc_initcap(const char *buff, size_t nbytes); extern Datum parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict, Oid *typid, int32 *typmod, int *tz, bool *have_error); +extern int datetime_format_flags(const char *fmt_str, bool *have_error); #endif diff --git a/src/include/utils/json.h b/src/include/utils/json.h index 7daf09f20e..f16cfdcb71 100644 --- a/src/include/utils/json.h +++ b/src/include/utils/json.h @@ -16,9 +16,35 @@ #include "lib/stringinfo.h" +typedef enum /* type categories for datum_to_json */ +{ + JSONTYPE_NULL, /* null, so we didn't bother to identify */ + JSONTYPE_BOOL, /* boolean (built-in types only) */ + JSONTYPE_NUMERIC, /* numeric (ditto) */ + JSONTYPE_DATE, /* we use special formatting for datetimes */ + JSONTYPE_TIMESTAMP, + JSONTYPE_TIMESTAMPTZ, + JSONTYPE_JSON, /* JSON itself (and JSONB) */ + JSONTYPE_ARRAY, /* array */ + JSONTYPE_COMPOSITE, /* composite */ + JSONTYPE_CAST, /* something with an explicit cast to JSON */ + JSONTYPE_OTHER /* all else */ +} JsonTypeCategory; + /* functions in json.c */ extern void escape_json(StringInfo buf, const char *str); extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); +extern bool to_json_is_immutable(Oid typoid); +extern void json_categorize_type(Oid typoid, JsonTypeCategory *tcategory, + Oid *outfuncoid); +extern Datum to_json_worker(Datum val, JsonTypeCategory tcategory, + Oid outfuncoid); +extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null, + bool unique_keys); +extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null); +extern bool json_validate(text *json, bool check_unique_keys, bool throw_error); #endif /* JSON_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 4e07debf78..be5cc0e397 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -329,6 +329,8 @@ typedef struct JsonbParseState JsonbValue contVal; Size size; struct JsonbParseState *next; + bool unique_keys; /* Check object key uniqueness */ + bool skip_nulls; /* Skip null object fields */ } JsonbParseState; /* @@ -374,6 +376,36 @@ typedef struct JsonbIterator struct JsonbIterator *parent; } JsonbIterator; +/* unlike with json categories, we need to treat json and jsonb differently */ +typedef enum /* type categories for datum_to_jsonb */ +{ + JSONBTYPE_NULL, /* null, so we didn't bother to identify */ + JSONBTYPE_BOOL, /* boolean (built-in types only) */ + JSONBTYPE_NUMERIC, /* numeric (ditto) */ + JSONBTYPE_DATE, /* we use special formatting for datetimes */ + JSONBTYPE_TIMESTAMP, /* we use special formatting for timestamp */ + JSONBTYPE_TIMESTAMPTZ, /* ... and timestamptz */ + JSONBTYPE_JSON, /* JSON */ + JSONBTYPE_JSONB, /* JSONB */ + JSONBTYPE_ARRAY, /* array */ + JSONBTYPE_COMPOSITE, /* composite */ + JSONBTYPE_JSONCAST, /* something with an explicit cast to JSON */ + JSONBTYPE_OTHER /* all else */ +} JsonbTypeCategory; + +/* values for the sql+json_type GUC. */ +typedef enum SqlJsonType +{ + SQLJSON_TYPE_JSON = 0, + SQLJSON_TYPE_JSONB = 1 +} SqlJsonType; + +#define SQLJSON_TYPE_IS_JSONB() (sql_json_type == SQLJSON_TYPE_JSONB) +#define SQLJSON_TYPE_OID() (SQLJSON_TYPE_IS_JSONB() ? JSONBOID : JSONOID) +#define SQLJSON_TYPE_NAME() (SQLJSON_TYPE_IS_JSONB() ? "jsonb" : "json") + +/* GUC */ +extern int sql_json_type; /* Support functions */ extern uint32 getJsonbOffset(const JsonbContainer *jc, int index); @@ -401,10 +433,14 @@ extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash, uint64 seed); /* jsonb.c support functions */ +extern Datum jsonb_from_text(text *js, bool unique_keys); extern char *JsonbToCString(StringInfo out, JsonbContainer *in, int estimated_len); extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern Jsonb *JsonbMakeEmptyArray(void); +extern Jsonb *JsonbMakeEmptyObject(void); +extern char *JsonbUnquote(Jsonb *jb); extern bool JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res); extern const char *JsonbTypeName(JsonbValue *jb); @@ -412,4 +448,15 @@ extern Datum jsonb_set_element(Jsonb *jb, Datum *path, int path_len, JsonbValue *newval); extern Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text); +extern bool to_jsonb_is_immutable(Oid typoid); +extern void jsonb_categorize_type(Oid typoid, JsonbTypeCategory *tcategory, + Oid *outfuncoid); +extern Datum to_jsonb_worker(Datum val, JsonbTypeCategory tcategory, + Oid outfuncoid); +extern Datum jsonb_build_object_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null, + bool unique_keys); +extern Datum jsonb_build_array_worker(int nargs, Datum *args, bool *nulls, + Oid *types, bool absent_on_null); + #endif /* __JSONB_H__ */ diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h index 0ca48591d0..9e9128e8b2 100644 --- a/src/include/utils/jsonfuncs.h +++ b/src/include/utils/jsonfuncs.h @@ -45,6 +45,9 @@ extern void pg_parse_json_or_ereport(JsonLexContext *lex, JsonSemAction *sem); /* report an error during json lexing or parsing */ extern void json_ereport_error(JsonParseErrorType error, JsonLexContext *lex); +/* get first JSON token */ +extern JsonTokenType json_get_first_token(text *json, bool throw_error); + extern uint32 parse_jsonb_index_flags(Jsonb *jb); extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state, JsonIterateStringValuesAction action); @@ -55,4 +58,8 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state, extern text *transform_json_string_values(text *json, void *action_state, JsonTransformStringValuesAction transform_action); +extern Datum json_populate_type(Datum json_val, Oid json_type, + Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull); + #endif diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 87d302b702..fd2663c22f 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -16,7 +16,9 @@ #include "fmgr.h" #include "nodes/pg_list.h" +#include "nodes/primnodes.h" #include "utils/jsonb.h" +#include "utils/jsonfuncs.h" typedef struct { @@ -174,6 +176,7 @@ extern bool jspGetBool(JsonPathItem *v); extern char *jspGetString(JsonPathItem *v, int32 *len); extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, int i); +extern bool jspIsMutable(JsonPath *path, List *varnames, List *varexprs); extern const char *jspOperationName(JsonPathItemType type); @@ -248,4 +251,34 @@ extern JsonPathParseResult *parsejsonpath(const char *str, int len); extern int jspConvertRegexFlags(uint32 xflags); +/* + * Evaluation of jsonpath + */ + +/* External variable passed into jsonpath. */ +typedef struct JsonPathVariableEvalContext +{ + char *name; + Oid typid; + int32 typmod; + struct ExprContext *econtext; + struct ExprState *estate; + Datum value; + bool isnull; + bool evaluated; +} JsonPathVariableEvalContext; + +/* SQL/JSON item */ +extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, + JsonbValue *res); + +extern bool JsonPathExists(Datum jb, JsonPath *path, List *vars, bool *error); +extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, bool *error, List *vars); +extern JsonbValue *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, + bool *error, List *vars); + +extern int EvalJsonPathVar(void *vars, char *varName, int varNameLen, + JsonbValue *val, JsonbValue *baseObject); + #endif diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index 1e24801a6f..5a8c2f3eef 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -47,6 +47,8 @@ my %replace_string = ( 'NOT_LA' => 'not', 'NULLS_LA' => 'nulls', 'WITH_LA' => 'with', + 'WITH_LA_UNIQUE' => 'with', + 'WITHOUT_LA' => 'without', 'TYPECAST' => '::', 'DOT_DOT' => '..', 'COLON_EQUALS' => ':=', diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c index a8571a3ffa..c47dee05cb 100644 --- a/src/interfaces/ecpg/preproc/parser.c +++ b/src/interfaces/ecpg/preproc/parser.c @@ -83,6 +83,7 @@ filtered_base_yylex(void) case WITH: case UIDENT: case USCONST: + case WITHOUT: break; default: return cur_token; @@ -143,6 +144,19 @@ filtered_base_yylex(void) case ORDINALITY: cur_token = WITH_LA; break; + case UNIQUE: + cur_token = WITH_LA_UNIQUE; + break; + } + break; + + case WITHOUT: + /* Replace WITHOUT by WITHOUT_LA if it's followed by TIME */ + switch (next_token) + { + case TIME: + cur_token = WITHOUT_LA; + break; } break; case UIDENT: diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out new file mode 100644 index 0000000000..bb62634314 --- /dev/null +++ b/src/test/regress/expected/json_sqljson.out @@ -0,0 +1,15 @@ +-- JSON_EXISTS +SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); +ERROR: JSON_EXISTS() is not yet implemented for json type +LINE 1: SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); + ^ +-- JSON_VALUE +SELECT JSON_VALUE(NULL FORMAT JSON, '$'); +ERROR: JSON_VALUE() is not yet implemented for json type +LINE 1: SELECT JSON_VALUE(NULL FORMAT JSON, '$'); + ^ +-- JSON_QUERY +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); +ERROR: JSON_QUERY() is not yet implemented for json type +LINE 1: SELECT JSON_QUERY(NULL FORMAT JSON, '$'); + ^ diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 1add673968..0ec940ad60 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -5509,3 +5509,133 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; 12345 (1 row) +-- test mapping of jsonb to SQL/JSON JSON type +select json(' { "aa": 1, "b" : 2 }'); + json +----------------------- + { "aa": 1, "b" : 2 } +(1 row) + +select json ' { "aa": 1, "b" : 2 }'; + json +----------------------- + { "aa": 1, "b" : 2 } +(1 row) + +select pg_catalog.json ' { "aa": 1, "b" : 2 }'; + json +----------------------- + { "aa": 1, "b" : 2 } +(1 row) + +create table test_json_as_json (js json, jb jsonb); +\d test_json_as_json + Table "public.test_json_as_json" + Column | Type | Collation | Nullable | Default +--------+-------+-----------+----------+--------- + js | json | | | + jb | jsonb | | | + +set sql_json = jsonb; +select json(' { "aa": 1, "b" : 2 }'); + json +------------------- + {"b": 2, "aa": 1} +(1 row) + +select json ' { "aa": 1, "b" : 2 }'; + jsonb +------------------- + {"b": 2, "aa": 1} +(1 row) + +select pg_catalog.json ' { "aa": 1, "b" : 2 }'; + json +----------------------- + { "aa": 1, "b" : 2 } +(1 row) + +\d test_json_as_json + Table "public.test_json_as_json" + Column | Type | Collation | Nullable | Default +--------+-----------------+-----------+----------+--------- + js | pg_catalog.json | | | + jb | json | | | + +create table test_json_as_jsonb (js json, jb jsonb, jt pg_catalog.json); +\d test_json_as_jsonb + Table "public.test_json_as_jsonb" + Column | Type | Collation | Nullable | Default +--------+-----------------+-----------+----------+--------- + js | json | | | + jb | json | | | + jt | pg_catalog.json | | | + +insert into test_json_as_jsonb values ('{ "a" : 1 }', '{ "a" : 1 }', '{ "a" : 1 }'); +select * from test_json_as_jsonb; + js | jb | jt +----------+----------+------------- + {"a": 1} | {"a": 1} | { "a" : 1 } +(1 row) + +select jsonb_object_field(js, 'a') from test_json_as_jsonb; + jsonb_object_field +-------------------- + 1 +(1 row) + +select jsonb_object_field(jb, 'a') from test_json_as_jsonb; + jsonb_object_field +-------------------- + 1 +(1 row) + +select jsonb_object_field(jt, 'a') from test_json_as_jsonb; +ERROR: function jsonb_object_field(pg_catalog.json, unknown) does not exist +LINE 1: select jsonb_object_field(jt, 'a') from test_json_as_jsonb; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select json_object_field(jt, 'a') from test_json_as_jsonb; + json_object_field +------------------- + 1 +(1 row) + +set sql_json = json; +\d test_json_as_jsonb + Table "public.test_json_as_jsonb" + Column | Type | Collation | Nullable | Default +--------+-------+-----------+----------+--------- + js | jsonb | | | + jb | jsonb | | | + jt | json | | | + +select * from test_json_as_jsonb; + js | jb | jt +----------+----------+------------- + {"a": 1} | {"a": 1} | { "a" : 1 } +(1 row) + +select jsonb_object_field(js, 'a') from test_json_as_jsonb; + jsonb_object_field +-------------------- + 1 +(1 row) + +select jsonb_object_field(jb, 'a') from test_json_as_jsonb; + jsonb_object_field +-------------------- + 1 +(1 row) + +select jsonb_object_field(jt, 'a') from test_json_as_jsonb; +ERROR: function jsonb_object_field(json, unknown) does not exist +LINE 1: select jsonb_object_field(jt, 'a') from test_json_as_jsonb; + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +select json_object_field(jt, 'a') from test_json_as_jsonb; + json_object_field +------------------- + 1 +(1 row) + diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out new file mode 100644 index 0000000000..f2f5e271b8 --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,1018 @@ +-- JSON_EXISTS +SELECT JSON_EXISTS(NULL::jsonb, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb 'null', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: jsonpath member accessor can only be applied to an object +SELECT JSON_EXISTS(jsonb 'null', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[]', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{}', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + json_exists +------------- + f +(1 row) + +-- extension: boolean expressions +SELECT JSON_EXISTS(jsonb '1', '$ > 2'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); + json_exists +------------- + t +(1 row) + +-- extension: RETURNING clause +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int); + json_exists +------------- + 1 +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int); + json_exists +------------- + 0 +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text); + json_exists +------------- + true +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text); + json_exists +------------- + false +(1 row) + +SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR); + json_exists +------------- + false +(1 row) + +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); +ERROR: cannot cast type boolean to jsonb +LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); + ^ +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); +ERROR: cannot cast type boolean to real +LINE 1: SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); + ^ +-- JSON_VALUE +SELECT JSON_VALUE(NULL::jsonb, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$'); + json_value +------------ + true +(1 row) + +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + json_value +------------ + t +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); + json_value +------------ + 123 +(1 row) + +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); +ERROR: SQL/JSON item cannot be cast to target type +SELECT JSON_VALUE(jsonb '1.23', '$'); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "1.23" +SELECT JSON_VALUE(jsonb '"aaa"', '$'); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); + json_value +------------ + aa +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "aaa" +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); + json_value +------------ + 111 +(1 row) + +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + ?column? +------------ + 03-01-2017 +(1 row) + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljsonb_int_not_null does not allow null values +SELECT JSON_VALUE(jsonb '[]', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: jsonpath member accessor can only be applied to an object +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); + json_value +------------ + error +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 3 +(1 row) + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); + json_value +------------ + 0 +(1 row) + +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: " " +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 5 +(1 row) + +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 1 +(1 row) + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + x | y +---+---- + 0 | -2 + 1 | 2 + 2 | -1 +(3 rows) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); + json_value +------------ + (1,2) +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + json_value +------------ + (1,2) +(1 row) + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); + json_value +------------------------------ + Tue Feb 20 18:34:56 2018 PST +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); + json_value +-------------------------- + Tue Feb 20 18:34:56 2018 +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_value +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- JSON_QUERY +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + json_query | json_query | json_query | json_query | json_query +--------------------+--------------------+--------------------+----------------------+---------------------- + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, null, "2"] | [1, null, "2"] | [1, null, "2"] | [[1, null, "2"]] | [[1, null, "2"]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] +(6 rows) + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + unspec | without | with cond | with uncond | with +--------------------+--------------------+---------------------+----------------------+---------------------- + | | | | + | | | | + null | null | [null] | [null] | [null] + 12.3 | 12.3 | [12.3] | [12.3] | [12.3] + true | true | [true] | [true] | [true] + "aaa" | "aaa" | ["aaa"] | ["aaa"] | ["aaa"] + [1, 2, 3] | [1, 2, 3] | [1, 2, 3] | [[1, 2, 3]] | [[1, 2, 3]] + {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | {"a": 1, "b": [2]} | [{"a": 1, "b": [2]}] | [{"a": 1, "b": [2]}] + | | [1, "2", null, [3]] | [1, "2", null, [3]] | [1, "2", null, [3]] +(9 rows) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: Token "aaa" is invalid. +CONTEXT: JSON data, line 1: aaa +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + json_query +------------ + \x616161 +(1 row) + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES)... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES)... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE... + ^ +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...N_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE... + ^ +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY); + json_query +------------ + "empty" +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +ERROR: JSON path expression in JSON_QUERY should return singleton item without wrapper +HINT: use WITH WRAPPER clause to wrap SQL/JSON item sequence into array +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR); + json_query +------------ + "empty" +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10)); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3)); + json_query +------------ + [1, +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea); + json_query +---------------- + \x5b312c20325d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON); + json_query +---------------- + \x5b312c20325d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + x | y | list +---+---+-------------- + 0 | 0 | [] + 0 | 1 | [1] + 0 | 2 | [1, 2] + 0 | 3 | [1, 2, 3] + 0 | 4 | [1, 2, 3, 4] + 1 | 0 | [] + 1 | 1 | [1] + 1 | 2 | [1, 2] + 1 | 3 | [1, 2, 3] + 1 | 4 | [1, 2, 3, 4] + 2 | 0 | [] + 2 | 1 | [] + 2 | 2 | [2] + 2 | 3 | [2, 3] + 2 | 4 | [2, 3, 4] + 3 | 0 | [] + 3 | 1 | [] + 3 | 2 | [] + 3 | 3 | [3] + 3 | 4 | [3, 4] + 4 | 0 | [] + 4 | 1 | [] + 4 | 2 | [] + 4 | 3 | [] + 4 | 4 | [4] +(25 rows) + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); + json_query +----------------------------------------------------- + (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",) +(1 row) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); + unnest +------------------------ + {"a": 1, "b": ["foo"]} + {"a": 2, "c": {}} + 123 +(3 rows) + +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); + json_query +-------------- + {1,2,NULL,3} +(1 row) + +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); + json_query +------------ + 1 +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); +ERROR: domain sqljsonb_int_not_null does not allow null values +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + json_query +----------------------------- + "2018-02-21T02:34:56+00:00" +(1 row) + +-- Test constraints +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C") + CONSTRAINT test_jsonb_constraint6 + CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2) +); +\d test_jsonb_constraints + Table "public.test_jsonb_constraints" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+-------------------------------------------------------------------------------- + js | text | | | + i | integer | | | + x | jsonb | | | JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER) +Check constraints: + "test_jsonb_constraint1" CHECK (js IS JSON) + "test_jsonb_constraint2" CHECK (JSON_EXISTS(js::jsonb, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr)) + "test_jsonb_constraint3" CHECK (JSON_VALUE(js::jsonb, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i) + "test_jsonb_constraint4" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C")) + "test_jsonb_constraint6" CHECK (JSON_EXISTS(js::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2) + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%'; + check_clause +-------------------------------------------------------------------------------------------------------------------------- + ((js IS JSON)) + (JSON_EXISTS((js)::jsonb, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr)) + ((JSON_VALUE((js)::jsonb, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > ('a'::bpchar COLLATE "C"))) + ((JSON_EXISTS((js)::jsonb, 'strict $."a"' RETURNING integer TRUE ON ERROR) < 2)) +(6 rows) + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + pg_get_expr +-------------------------------------------------------------------------------- + JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER) +(1 row) + +INSERT INTO test_jsonb_constraints VALUES ('', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint1" +DETAIL: Failing row contains (, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('1', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains (1, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('[]'); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains ([], null, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint2" +DETAIL: Failing row contains ({"b": 1}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint3" +DETAIL: Failing row contains ({"a": 1}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint5" +DETAIL: Failing row contains ({"a": 7}, 1, [1, 2]). +INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); +ERROR: new row for relation "test_jsonb_constraints" violates check constraint "test_jsonb_constraint4" +DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]). +DROP TABLE test_jsonb_constraints; +-- Test mutabilily od query functions +CREATE TABLE test_jsonb_mutability(js jsonb); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))')); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +ERROR: functions in index expression must be marked IMMUTABLE +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x)); +DROP TABLE test_jsonb_mutability; diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 562b586d8e..e7d6358a25 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1473,8 +1473,10 @@ WHERE a.aggfnoid = p.oid AND NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2])) OR (p.pronargs > 2 AND NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3])) - -- we could carry the check further, but 3 args is enough for now - OR (p.pronargs > 3) + OR (p.pronargs > 3 AND + NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4])) + -- we could carry the check further, but 4 args is enough for now + OR (p.pronargs > 4) ); aggfnoid | proname | oid | proname ----------+---------+-----+--------- diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out new file mode 100644 index 0000000000..51bd216120 --- /dev/null +++ b/src/test/regress/expected/sqljson.out @@ -0,0 +1,1352 @@ +-- JSON() +SELECT JSON(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON(); + ^ +SELECT JSON(NULL); + json +------ + +(1 row) + +SELECT JSON('{ "a" : 1 } '); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT JSON('{ "a" : 1 } ' FORMAT JSON); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); + ^ +SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8); + json +-------------- + { "a" : 1 } +(1 row) + +SELECT pg_typeof(JSON('{ "a" : 1 } ')); + pg_typeof +----------- + json +(1 row) + +SELECT JSON(' 1 '::json); + json +--------- + 1 +(1 row) + +SELECT JSON(' 1 '::jsonb); + json +------ + 1 +(1 row) + +SELECT JSON(' 1 '::json WITH UNIQUE KEYS); +ERROR: cannot use non-string types with WITH UNIQUE KEYS clause +LINE 1: SELECT JSON(' 1 '::json WITH UNIQUE KEYS); + ^ +SELECT JSON(123); +ERROR: cannot cast type integer to json +LINE 1: SELECT JSON(123); + ^ +SELECT JSON('{"a": 1, "a": 2}'); + json +------------------ + {"a": 1, "a": 2} +(1 row) + +SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS); +ERROR: duplicate JSON object key value +SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS); + json +------------------ + {"a": 1, "a": 2} +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON); + QUERY PLAN +----------------------------------------------- + Result + Output: JSON('\x313233'::bytea FORMAT JSON) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8); + QUERY PLAN +------------------------------------------------------------- + Result + Output: JSON('\x313233'::bytea FORMAT JSON ENCODING UTF8) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS); + QUERY PLAN +---------------------------------------------- + Result + Output: JSON('123'::text WITH UNIQUE KEYS) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +SELECT JSON('123' RETURNING text); +ERROR: cannot use RETURNING type text in JSON() +LINE 1: SELECT JSON('123' RETURNING text); + ^ +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); + QUERY PLAN +---------------------------------------------- + Result + Output: JSON('123'::jsonb RETURNING jsonb) +(2 rows) + +SELECT pg_typeof(JSON('123')); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING json)); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING jsonb)); + pg_typeof +----------- + jsonb +(1 row) + +SET sql_json = jsonb; +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); + QUERY PLAN +----------------------------- + Result + Output: JSON('123'::json) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING pg_catalog.json); + QUERY PLAN +------------------------------------------------------------------ + Result + Output: JSON('123'::pg_catalog.json RETURNING pg_catalog.json) +(2 rows) + +SELECT pg_typeof(JSON('123')); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING json)); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING jsonb)); + pg_typeof +----------- + json +(1 row) + +SELECT pg_typeof(JSON('123' RETURNING pg_catalog.json)); + pg_typeof +----------------- + pg_catalog.json +(1 row) + +SET sql_json = json; +-- JSON_SCALAR() +SELECT JSON_SCALAR(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON_SCALAR(); + ^ +SELECT JSON_SCALAR(NULL); + json_scalar +------------- + +(1 row) + +SELECT JSON_SCALAR(NULL::int); + json_scalar +------------- + +(1 row) + +SELECT JSON_SCALAR(123); + json_scalar +------------- + 123 +(1 row) + +SELECT JSON_SCALAR(123.45); + json_scalar +------------- + 123.45 +(1 row) + +SELECT JSON_SCALAR(123.45::numeric); + json_scalar +------------- + 123.45 +(1 row) + +SELECT JSON_SCALAR(true); + json_scalar +------------- + true +(1 row) + +SELECT JSON_SCALAR(false); + json_scalar +------------- + false +(1 row) + +SELECT JSON_SCALAR(' 123.45'); + json_scalar +------------- + " 123.45" +(1 row) + +SELECT JSON_SCALAR('2020-06-07'::date); + json_scalar +-------------- + "2020-06-07" +(1 row) + +SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp); + json_scalar +----------------------- + "2020-06-07T01:02:03" +(1 row) + +SELECT JSON_SCALAR('{}'::json); + json_scalar +------------- + {} +(1 row) + +SELECT JSON_SCALAR('{}'::jsonb); + json_scalar +------------- + {} +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123'); + QUERY PLAN +------------------------------------ + Result + Output: JSON_SCALAR('123'::text) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); + QUERY PLAN +-------------------------------------------- + Result + Output: JSON_SCALAR(123 RETURNING jsonb) +(2 rows) + +SET sql_json = jsonb; +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); + QUERY PLAN +---------------------------- + Result + Output: JSON_SCALAR(123) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING pg_catalog.json); + QUERY PLAN +------------------------------------------------------ + Result + Output: JSON_SCALAR(123 RETURNING pg_catalog.json) +(2 rows) + +SET sql_json = json; +-- JSON_SERIALIZE() +SELECT JSON_SERIALIZE(); +ERROR: syntax error at or near ")" +LINE 1: SELECT JSON_SERIALIZE(); + ^ +SELECT JSON_SERIALIZE(NULL); + json_serialize +---------------- + +(1 row) + +SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } ')); + json_serialize +---------------- + { "a" : 1 } +(1 row) + +SELECT JSON_SERIALIZE('{ "a" : 1 } '); + json_serialize +---------------- + { "a" : 1 } +(1 row) + +SELECT JSON_SERIALIZE('1'); + json_serialize +---------------- + 1 +(1 row) + +SELECT JSON_SERIALIZE('1' FORMAT JSON); + json_serialize +---------------- + 1 +(1 row) + +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea); + json_serialize +---------------------------- + \x7b20226122203a2031207d20 +(1 row) + +SELECT pg_typeof(JSON_SERIALIZE(NULL)); + pg_typeof +----------- + text +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}'); + QUERY PLAN +----------------------------------------------------- + Result + Output: JSON_SERIALIZE('{}'::json RETURNING text) +(2 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea); + QUERY PLAN +------------------------------------------------------ + Result + Output: JSON_SERIALIZE('{}'::json RETURNING bytea) +(2 rows) + +-- JSON_OBJECT() +SELECT JSON_OBJECT(); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING json); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING json FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING jsonb); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text FORMAT JSON); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8); +ERROR: cannot set JSON encoding for non-bytea output types +LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8)... + ^ +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +ERROR: unrecognized JSON encoding: invalid_encoding +SELECT JSON_OBJECT(RETURNING bytea); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8); + json_object +------------- + \x7b7d +(1 row) + +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF1... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF3... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); +ERROR: cannot use non-string types with explicit FORMAT JSON clause +LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); + ^ +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF... + ^ +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); +WARNING: FORMAT JSON has no effect for json and jsonb types +LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); + ^ + json_object +---------------- + {"foo" : null} +(1 row) + +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UT... + ^ +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); +WARNING: FORMAT JSON has no effect for json and jsonb types +LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); + ^ + json_object +--------------- + {"foo": null} +(1 row) + +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8); +ERROR: JSON ENCODING clause is only allowed for bytea input type +LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING U... + ^ +SELECT JSON_OBJECT(NULL: 1); +ERROR: argument 1 cannot be null +HINT: Object keys should be text. +SELECT JSON_OBJECT('a': 2 + 3); + json_object +------------- + {"a" : 5} +(1 row) + +SELECT JSON_OBJECT('a' VALUE 2 + 3); + json_object +------------- + {"a" : 5} +(1 row) + +--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3); +SELECT JSON_OBJECT('a' || 2: 1); + json_object +------------- + {"a2" : 1} +(1 row) + +SELECT JSON_OBJECT(('a' || 2) VALUE 1); + json_object +------------- + {"a2" : 1} +(1 row) + +--SELECT JSON_OBJECT('a' || 2 VALUE 1); +--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1); +SELECT JSON_OBJECT('a': 2::text); + json_object +------------- + {"a" : "2"} +(1 row) + +SELECT JSON_OBJECT('a' VALUE 2::text); + json_object +------------- + {"a" : "2"} +(1 row) + +--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text); +SELECT JSON_OBJECT(1::text: 2); + json_object +------------- + {"1" : 2} +(1 row) + +SELECT JSON_OBJECT((1::text) VALUE 2); + json_object +------------- + {"1" : 2} +(1 row) + +--SELECT JSON_OBJECT(1::text VALUE 2); +--SELECT JSON_OBJECT(KEY 1::text VALUE 2); +SELECT JSON_OBJECT(json '[1]': 123); +ERROR: key value must be scalar, not array, composite, or json +SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa'); +ERROR: key value must be scalar, not array, composite, or json +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' +); + json_object +------------------------------------------------------------------- + {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123} +(1 row) + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' + RETURNING jsonb +); + json_object +------------------------------------------------------------------- + {"a": "123", "c": [1, true, {}], "d": {"x": 123.45}, "1.23": 123} +(1 row) + +/* +SELECT JSON_OBJECT( + 'a': '123', + KEY 1.23 VALUE 123, + 'c' VALUE json '[1, true, {}]' +); +*/ +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa')); + json_object +----------------------------------------------- + {"a" : "123", "b" : {"a" : 111, "b" : "aaa"}} +(1 row) + +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb)); + json_object +------------------------------------------- + {"a": "123", "b": {"a": 111, "b": "aaa"}} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text)); + json_object +----------------------- + {"a" : "{\"b\" : 1}"} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON); + json_object +------------------- + {"a" : {"b" : 1}} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea)); + json_object +--------------------------------- + {"a" : "\\x7b226222203a20317d"} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON); + json_object +------------------- + {"a" : {"b" : 1}} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); + json_object +---------------------------------- + {"a" : "1", "b" : null, "c" : 2} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); + json_object +---------------------------------- + {"a" : "1", "b" : null, "c" : 2} +(1 row) + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); + json_object +---------------------- + {"a" : "1", "c" : 2} +(1 row) + +SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE); + json_object +-------------------- + {"1" : 1, "1" : 1} +(1 row) + +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb); + json_object +------------- + {"1": 1} +(1 row) + +SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb); + json_object +---------------------------- + {"1": 1, "3": 1, "5": "a"} +(1 row) + +-- JSON_ARRAY() +SELECT JSON_ARRAY(); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING json); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING json FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING jsonb); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text FORMAT JSON); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); +ERROR: cannot set JSON encoding for non-bytea output types +LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); + ^ +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +ERROR: unrecognized JSON encoding: invalid_encoding +SELECT JSON_ARRAY(RETURNING bytea); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8); + json_array +------------ + \x5b5d +(1 row) + +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32); +ERROR: unsupported JSON encoding +LINE 1: SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32... + ^ +HINT: only UTF8 JSON encoding is supported +SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); + json_array +----------------------------------------------------- + ["aaa", 111, true, [1, 2, 3], {"a": [1]}, ["a", 3]] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL); + json_array +------------------ + ["a", null, "b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL); + json_array +------------ + ["a", "b"] +(1 row) + +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL); + json_array +------------ + ["b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb); + json_array +------------------ + ["a", null, "b"] +(1 row) + +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb); + json_array +------------ + ["a", "b"] +(1 row) + +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb); + json_array +------------ + ["b"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text)); + json_array +------------------------------- + ["[\"{ \\\"a\\\" : 123 }\"]"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text)); + json_array +----------------------- + ["[{ \"a\" : 123 }]"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON); + json_array +------------------- + [[{ "a" : 123 }]] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i)); + json_array +------------ + [1, 2, 4] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i)); + json_array +------------ + [[1,2], + + [3,4]] +(1 row) + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb); + json_array +------------------ + [[1, 2], [3, 4]] +(1 row) + +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL); +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); + json_array +------------ + [1, 2, 3] +(1 row) + +-- Should fail +SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); + ^ +SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); + ^ +SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); +ERROR: subquery must return only one column +LINE 1: SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); + ^ +-- JSON_ARRAYAGG() +SELECT JSON_ARRAYAGG(i) IS NULL, + JSON_ARRAYAGG(i RETURNING jsonb) IS NULL +FROM generate_series(1, 0) i; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +SELECT JSON_ARRAYAGG(i), + JSON_ARRAYAGG(i RETURNING jsonb) +FROM generate_series(1, 5) i; + json_arrayagg | json_arrayagg +-----------------+----------------- + [1, 2, 3, 4, 5] | [1, 2, 3, 4, 5] +(1 row) + +SELECT JSON_ARRAYAGG(i ORDER BY i DESC) +FROM generate_series(1, 5) i; + json_arrayagg +----------------- + [5, 4, 3, 2, 1] +(1 row) + +SELECT JSON_ARRAYAGG(i::text::json) +FROM generate_series(1, 5) i; + json_arrayagg +----------------- + [1, 2, 3, 4, 5] +(1 row) + +SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON) +FROM generate_series(1, 5) i; + json_arrayagg +------------------------------------------ + [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]] +(1 row) + +SELECT JSON_ARRAYAGG(NULL), + JSON_ARRAYAGG(NULL RETURNING jsonb) +FROM generate_series(1, 5); + json_arrayagg | json_arrayagg +---------------+--------------- + [] | [] +(1 row) + +SELECT JSON_ARRAYAGG(NULL NULL ON NULL), + JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb) +FROM generate_series(1, 5); + json_arrayagg | json_arrayagg +--------------------------------+-------------------------------- + [null, null, null, null, null] | [null, null, null, null, null] +(1 row) + +SELECT + JSON_ARRAYAGG(bar), + JSON_ARRAYAGG(bar RETURNING jsonb), + JSON_ARRAYAGG(bar ABSENT ON NULL), + JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb), + JSON_ARRAYAGG(bar NULL ON NULL), + JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb), + JSON_ARRAYAGG(foo), + JSON_ARRAYAGG(foo RETURNING jsonb), + JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2), + JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar); + json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg | json_arrayagg +-----------------+-----------------+-----------------+-----------------+-----------------------------------------+-----------------------------------------+-----------------+--------------------------------------------------------------------------------------------------------------------------+---------------+-------------------------------------- + [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [3, 1, 5, 2, 4] | [null, 3, 1, null, null, 5, 2, 4, null] | [null, 3, 1, null, null, 5, 2, 4, null] | [{"bar":null}, +| [{"bar": null}, {"bar": 3}, {"bar": 1}, {"bar": null}, {"bar": null}, {"bar": 5}, {"bar": 2}, {"bar": 4}, {"bar": null}] | [{"bar":3}, +| [{"bar": 3}, {"bar": 4}, {"bar": 5}] + | | | | | | {"bar":3}, +| | {"bar":4}, +| + | | | | | | {"bar":1}, +| | {"bar":5}] | + | | | | | | {"bar":null}, +| | | + | | | | | | {"bar":null}, +| | | + | | | | | | {"bar":5}, +| | | + | | | | | | {"bar":2}, +| | | + | | | | | | {"bar":4}, +| | | + | | | | | | {"bar":null}] | | | +(1 row) + +SELECT + bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar); + bar | json_arrayagg +-----+--------------- + 4 | [4, 4] + 4 | [4, 4] + 2 | [4, 4] + 5 | [5, 3, 5] + 3 | [5, 3, 5] + 1 | [5, 3, 5] + 5 | [5, 3, 5] + | + | + | + | +(11 rows) + +-- JSON_OBJECTAGG() +SELECT JSON_OBJECTAGG('key': 1) IS NULL, + JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL +WHERE FALSE; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +SELECT JSON_OBJECTAGG(NULL: 1); +ERROR: field name must not be null +SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb); +ERROR: field name must not be null +SELECT + JSON_OBJECTAGG(i: i), +-- JSON_OBJECTAGG(i VALUE i), +-- JSON_OBJECTAGG(KEY i VALUE i), + JSON_OBJECTAGG(i: i RETURNING jsonb) +FROM + generate_series(1, 5) i; + json_objectagg | json_objectagg +-------------------------------------------------+------------------------------------------ + { "1" : 1, "2" : 2, "3" : 3, "4" : 4, "5" : 5 } | {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5} +(1 row) + +SELECT + JSON_OBJECTAGG(k: v), + JSON_OBJECTAGG(k: v NULL ON NULL), + JSON_OBJECTAGG(k: v ABSENT ON NULL), + JSON_OBJECTAGG(k: v RETURNING jsonb), + JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb), + JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb) +FROM + (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v); + json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg | json_objectagg +----------------------------------------------+----------------------------------------------+----------------------+--------------------------------+--------------------------------+------------------ + { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "1" : null, "2" : null, "3" : 3 } | { "1" : 1, "3" : 3 } | {"1": null, "2": null, "3": 3} | {"1": null, "2": null, "3": 3} | {"1": 1, "3": 3} +(1 row) + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON key "1" +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v); + json_objectagg +---------------------- + { "1" : 1, "2" : 2 } +(1 row) + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON object key value +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); +ERROR: duplicate JSON object key value +-- Test JSON_OBJECT deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + QUERY PLAN +------------------------------------------------------------------------------ + Result + Output: JSON_OBJECT('foo' : '1'::json, 'bar' : 'baz'::text RETURNING json) +(2 rows) + +CREATE VIEW json_object_view AS +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); +\sv json_object_view +CREATE OR REPLACE VIEW public.json_object_view AS + SELECT JSON_OBJECT('foo' : '1'::text FORMAT JSON, 'bar' : 'baz'::text RETURNING json) AS "json_object" +DROP VIEW json_object_view; +-- Test JSON_ARRAY deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + QUERY PLAN +--------------------------------------------------- + Result + Output: JSON_ARRAY('1'::json, 2 RETURNING json) +(2 rows) + +CREATE VIEW json_array_view AS +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); +\sv json_array_view +CREATE OR REPLACE VIEW public.json_array_view AS + SELECT JSON_ARRAY('1'::text FORMAT JSON, 2 RETURNING json) AS "json_array" +DROP VIEW json_array_view; +-- Test JSON_OBJECTAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE (i > 3)) + -> Function Scan on pg_catalog.generate_series i + Output: i + Function Call: generate_series(1, 5) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------- + WindowAgg + Output: JSON_OBJECTAGG(i : (('111'::text || (i)::text))::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) OVER (?), ((i % 2)) + -> Sort + Output: ((i % 2)), i + Sort Key: ((i.i % 2)) + -> Function Scan on pg_catalog.generate_series i + Output: (i % 2), i + Function Call: generate_series(1, 5) +(8 rows) + +CREATE VIEW json_objectagg_view AS +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; +\sv json_objectagg_view +CREATE OR REPLACE VIEW public.json_objectagg_view AS + SELECT JSON_OBJECTAGG(i.i : ('111'::text || i.i)::bytea FORMAT JSON WITH UNIQUE KEYS RETURNING text) FILTER (WHERE i.i > 3) AS "json_objectagg" + FROM generate_series(1, 5) i(i) +DROP VIEW json_objectagg_view; +-- Test JSON_ARRAYAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE (i > 3)) + -> Function Scan on pg_catalog.generate_series i + Output: i + Function Call: generate_series(1, 5) +(5 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------- + WindowAgg + Output: JSON_ARRAYAGG((('111'::text || (i)::text))::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (?), ((i % 2)) + -> Sort + Output: ((i % 2)), i + Sort Key: ((i.i % 2)) + -> Function Scan on pg_catalog.generate_series i + Output: (i % 2), i + Function Call: generate_series(1, 5) +(8 rows) + +CREATE VIEW json_arrayagg_view AS +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; +\sv json_arrayagg_view +CREATE OR REPLACE VIEW public.json_arrayagg_view AS + SELECT JSON_ARRAYAGG(('111'::text || i.i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i.i > 3) AS "json_arrayagg" + FROM generate_series(1, 5) i(i) +DROP VIEW json_arrayagg_view; +-- Test JSON_ARRAY(subquery) deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); + QUERY PLAN +--------------------------------------------------------------------- + Result + Output: $0 + InitPlan 1 (returns $0) + -> Aggregate + Output: JSON_ARRAYAGG("*VALUES*".column1 RETURNING jsonb) + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(7 rows) + +CREATE VIEW json_array_subquery_view AS +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); +\sv json_array_subquery_view +CREATE OR REPLACE VIEW public.json_array_subquery_view AS + SELECT ( SELECT JSON_ARRAYAGG(q.a RETURNING jsonb) AS "json_arrayagg" + FROM ( SELECT foo.i + FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array" +DROP VIEW json_array_subquery_view; +-- IS JSON predicate +SELECT NULL IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL IS NOT JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::json IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::jsonb IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::text IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::bytea IS JSON; + ?column? +---------- + +(1 row) + +SELECT NULL::int IS JSON; +ERROR: cannot use type integer in IS JSON predicate +SELECT '' IS JSON; + ?column? +---------- + f +(1 row) + +SELECT bytea '\x00' IS JSON; +ERROR: invalid byte sequence for encoding "UTF8": 0x00 +CREATE TABLE test_is_json (js text); +INSERT INTO test_is_json VALUES + (NULL), + (''), + ('123'), + ('"aaa "'), + ('true'), + ('null'), + ('[]'), + ('[1, "2", {}]'), + ('{}'), + ('{ "a": 1, "b": null }'), + ('{ "a": 1, "a": null }'), + ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'), + ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'), + ('aaa'), + ('{a:1}'), + ('["a",]'); +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + test_is_json; + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + | | | | | | | | + | f | t | f | f | f | f | f | f + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f + aaa | f | t | f | f | f | f | f | f + {a:1} | f | t | f | f | f | f | f | f + ["a",] | f | t | f | f | f | f | f | f +(16 rows) + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js); + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f +(11 rows) + +SELECT + js0, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js); + js0 | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f +(11 rows) + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js); + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE +-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+------------- + 123 | t | f | t | f | f | t | t | t + "aaa " | t | f | t | f | f | t | t | t + true | t | f | t | f | f | t | t | t + null | t | f | t | f | f | t | t | t + [] | t | f | t | f | t | f | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t + {} | t | f | t | t | f | f | t | t + {"a": 1, "b": null} | t | f | t | t | f | f | t | t + {"a": null} | t | f | t | t | f | f | t | t + {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t + {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t +(11 rows) + +-- Test IS JSON deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Function Scan on pg_catalog.generate_series i + Output: ('1'::text IS JSON), (('1'::text || (i)::text) IS JSON SCALAR), (NOT ('[]'::text IS JSON ARRAY)), ('{}'::text IS JSON OBJECT WITH UNIQUE KEYS) + Function Call: generate_series(1, 3) +(3 rows) + +CREATE VIEW is_json_view AS +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; +\sv is_json_view +CREATE OR REPLACE VIEW public.is_json_view AS + SELECT '1'::text IS JSON AS "any", + ('1'::text || i.i) IS JSON SCALAR AS scalar, + NOT '[]'::text IS JSON ARRAY AS "array", + '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object + FROM generate_series(1, 3) i(i) +DROP VIEW is_json_view; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 7be89178f0..810450164d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -108,7 +108,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # ---------- # Another group of parallel tests (JSON related) # ---------- -test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath +test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson json_sqljson jsonb_sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql new file mode 100644 index 0000000000..4f30fa46b9 --- /dev/null +++ b/src/test/regress/sql/json_sqljson.sql @@ -0,0 +1,11 @@ +-- JSON_EXISTS + +SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL FORMAT JSON, '$'); + +-- JSON_QUERY + +SELECT JSON_QUERY(NULL FORMAT JSON, '$'); diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 5016f29c15..4af2a0cb11 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -1482,3 +1482,41 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; + +-- test mapping of jsonb to SQL/JSON JSON type +select json(' { "aa": 1, "b" : 2 }'); +select json ' { "aa": 1, "b" : 2 }'; +select pg_catalog.json ' { "aa": 1, "b" : 2 }'; + +create table test_json_as_json (js json, jb jsonb); +\d test_json_as_json + +set sql_json = jsonb; + +select json(' { "aa": 1, "b" : 2 }'); +select json ' { "aa": 1, "b" : 2 }'; +select pg_catalog.json ' { "aa": 1, "b" : 2 }'; + +\d test_json_as_json + +create table test_json_as_jsonb (js json, jb jsonb, jt pg_catalog.json); +\d test_json_as_jsonb + +insert into test_json_as_jsonb values ('{ "a" : 1 }', '{ "a" : 1 }', '{ "a" : 1 }'); + +select * from test_json_as_jsonb; + +select jsonb_object_field(js, 'a') from test_json_as_jsonb; +select jsonb_object_field(jb, 'a') from test_json_as_jsonb; +select jsonb_object_field(jt, 'a') from test_json_as_jsonb; +select json_object_field(jt, 'a') from test_json_as_jsonb; + +set sql_json = json; +\d test_json_as_jsonb + +select * from test_json_as_jsonb; + +select jsonb_object_field(js, 'a') from test_json_as_jsonb; +select jsonb_object_field(jb, 'a') from test_json_as_jsonb; +select jsonb_object_field(jt, 'a') from test_json_as_jsonb; +select json_object_field(jt, 'a') from test_json_as_jsonb; diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql new file mode 100644 index 0000000000..00a067a06a --- /dev/null +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -0,0 +1,317 @@ +-- JSON_EXISTS + +SELECT JSON_EXISTS(NULL::jsonb, '$'); + +SELECT JSON_EXISTS(jsonb '[]', '$'); +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); + +SELECT JSON_EXISTS(jsonb '1', '$'); +SELECT JSON_EXISTS(jsonb 'null', '$'); +SELECT JSON_EXISTS(jsonb '[]', '$'); + +SELECT JSON_EXISTS(jsonb '1', '$.a'); +SELECT JSON_EXISTS(jsonb '1', 'strict $.a'); +SELECT JSON_EXISTS(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_EXISTS(jsonb 'null', '$.a'); +SELECT JSON_EXISTS(jsonb '[]', '$.a'); +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'strict $.a'); +SELECT JSON_EXISTS(jsonb '[1, "aaa", {"a": 1}]', 'lax $.a'); +SELECT JSON_EXISTS(jsonb '{}', '$.a'); +SELECT JSON_EXISTS(jsonb '{"b": 1, "a": 2}', '$.a'); + +SELECT JSON_EXISTS(jsonb '1', '$.a.b'); +SELECT JSON_EXISTS(jsonb '{"a": {"b": 1}}', '$.a.b'); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.a.b'); + +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); +SELECT JSON_EXISTS(jsonb '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + +-- extension: boolean expressions +SELECT JSON_EXISTS(jsonb '1', '$ > 2'); +SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); + +-- extension: RETURNING clause +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING bool); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING bool); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING int); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING int); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING text); +SELECT JSON_EXISTS(jsonb '1', '$[1]' RETURNING text); +SELECT JSON_EXISTS(jsonb '1', 'strict $[1]' RETURNING text FALSE ON ERROR); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING jsonb); +SELECT JSON_EXISTS(jsonb '1', '$[0]' RETURNING float4); + + +-- JSON_VALUE + +SELECT JSON_VALUE(NULL::jsonb, '$'); + +SELECT JSON_VALUE(jsonb 'null', '$'); +SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); + +SELECT JSON_VALUE(jsonb 'true', '$'); +SELECT JSON_VALUE(jsonb 'true', '$' RETURNING bool); + +SELECT JSON_VALUE(jsonb '123', '$'); +SELECT JSON_VALUE(jsonb '123', '$' RETURNING int) + 234; +SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); +/* jsonb bytea ??? */ +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1.23', '$'); +SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING numeric); +SELECT JSON_VALUE(jsonb '"1.23"', '$' RETURNING int ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '"aaa"', '$'); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(5)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING char(2)); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING json ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING json); +SELECT JSON_VALUE(jsonb '"\"aaa\""', '$' RETURNING jsonb); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); +SELECT JSON_VALUE(jsonb '"123"', '$' RETURNING int) + 234; + +SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9; + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$.a' RETURNING sqljsonb_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(jsonb '[]', '$'); +SELECT JSON_VALUE(jsonb '[]', '$' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '{}', '$'); +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); + +SELECT JSON_VALUE(jsonb '1', '$.a'); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 'error' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'strict $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); +SELECT JSON_VALUE(jsonb '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[1,2]', '$[*]' DEFAULT '0' ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(jsonb '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(jsonb '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + +SELECT + x, + JSON_VALUE( + jsonb '{"a": 1, "b": 2}', + '$.* ? (@ > $x)' PASSING x AS x + RETURNING int + DEFAULT -1 ON EMPTY + DEFAULT -2 ON ERROR + ) y +FROM + generate_series(0, 2) x; + +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a); +SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + +-- Test timestamptz passing and output +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- JSON_QUERY + +SELECT + JSON_QUERY(js, '$'), + JSON_QUERY(js, '$' WITHOUT WRAPPER), + JSON_QUERY(js, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + (jsonb 'null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + +SELECT + JSON_QUERY(js, 'strict $[*]') AS "unspec", + JSON_QUERY(js, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + (jsonb '1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +-- Should succeed +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + +SELECT JSON_QUERY(jsonb '[]', '$[*]'); +SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' DEFAULT '"empty"' ON EMPTY); + +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR); + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' DEFAULT '"empty"' ON ERROR); + +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING jsonb FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(10)); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING char(3)); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea); +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON); + +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + +SELECT + x, y, + JSON_QUERY( + jsonb '[1,2,3,4,5,null]', + '$[*] ? (@ >= $x && @ <= $y)' + PASSING x AS x, y AS y + WITH CONDITIONAL WRAPPER + EMPTY ARRAY ON EMPTY + ) list +FROM + generate_series(0, 4) x, + generate_series(0, 4) y; + +-- Extension: record types returning +CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]); + +SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa); +SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca); + +-- Extension: array types returning +SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); +SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[])); + +-- Extension: domain types returning +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); +SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null); + +-- Test timestamptz passing and output +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_QUERY(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + +-- Test constraints + +CREATE TABLE test_jsonb_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_jsonb_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_jsonb_constraint2 + CHECK (JSON_EXISTS(js::jsonb, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_jsonb_constraint3 + CHECK (JSON_VALUE(js::jsonb, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_jsonb_constraint4 + CHECK (JSON_QUERY(js::jsonb, '$.a' WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_jsonb_constraint5 + CHECK (JSON_QUERY(js::jsonb, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a' COLLATE "C") + CONSTRAINT test_jsonb_constraint6 + CHECK (JSON_EXISTS(js::jsonb, 'strict $.a' RETURNING int TRUE ON ERROR) < 2) +); + +\d test_jsonb_constraints + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_jsonb_constraint%'; + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + +INSERT INTO test_jsonb_constraints VALUES ('', 1); +INSERT INTO test_jsonb_constraints VALUES ('1', 1); +INSERT INTO test_jsonb_constraints VALUES ('[]'); +INSERT INTO test_jsonb_constraints VALUES ('{"b": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 1}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); +INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); + +DROP TABLE test_jsonb_constraints; + +-- Test mutabilily od query functions +CREATE TABLE test_jsonb_mutability(js jsonb); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a[0]')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime()')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@ < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime())')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime() < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("HH:MI TZH"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.a ? (@.datetime("HH:MI TZH") < $.datetime("YY-MM-DD HH:MI"))')); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("HH:MI TZH") < $y' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '12:34'::timetz AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() < $x' PASSING '1234'::int AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime() ? (@ == $x)' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$.datetime("YY-MM-DD") ? (@ == $x)' PASSING '2020-07-14'::date AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, 0 to $.a ? (@.datetime() == $x)]' PASSING '12:34'::time AS x)); +CREATE INDEX ON test_jsonb_mutability (JSON_QUERY(js, '$[1, $.a ? (@.datetime("HH:MI") == $x)]' PASSING '12:34'::time AS x)); +DROP TABLE test_jsonb_mutability; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 5a9c479692..7a849ae363 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -854,8 +854,10 @@ WHERE a.aggfnoid = p.oid AND NOT binary_coercible(p.proargtypes[1], ptr.proargtypes[2])) OR (p.pronargs > 2 AND NOT binary_coercible(p.proargtypes[2], ptr.proargtypes[3])) - -- we could carry the check further, but 3 args is enough for now - OR (p.pronargs > 3) + OR (p.pronargs > 3 AND + NOT binary_coercible(p.proargtypes[3], ptr.proargtypes[4])) + -- we could carry the check further, but 4 args is enough for now + OR (p.pronargs > 4) ); -- Cross-check finalfn (if present) against its entry in pg_proc. diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql new file mode 100644 index 0000000000..4ff6076763 --- /dev/null +++ b/src/test/regress/sql/sqljson.sql @@ -0,0 +1,467 @@ +-- JSON() +SELECT JSON(); +SELECT JSON(NULL); +SELECT JSON('{ "a" : 1 } '); +SELECT JSON('{ "a" : 1 } ' FORMAT JSON); +SELECT JSON('{ "a" : 1 } ' FORMAT JSON ENCODING UTF8); +SELECT JSON('{ "a" : 1 } '::bytea FORMAT JSON ENCODING UTF8); +SELECT pg_typeof(JSON('{ "a" : 1 } ')); + +SELECT JSON(' 1 '::json); +SELECT JSON(' 1 '::jsonb); +SELECT JSON(' 1 '::json WITH UNIQUE KEYS); +SELECT JSON(123); + +SELECT JSON('{"a": 1, "a": 2}'); +SELECT JSON('{"a": 1, "a": 2}' WITH UNIQUE KEYS); +SELECT JSON('{"a": 1, "a": 2}' WITHOUT UNIQUE KEYS); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' FORMAT JSON); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'::bytea FORMAT JSON ENCODING UTF8); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITH UNIQUE KEYS); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' WITHOUT UNIQUE KEYS); + +SELECT JSON('123' RETURNING text); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); +SELECT pg_typeof(JSON('123')); +SELECT pg_typeof(JSON('123' RETURNING json)); +SELECT pg_typeof(JSON('123' RETURNING jsonb)); + +SET sql_json = jsonb; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING jsonb); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON('123' RETURNING pg_catalog.json); +SELECT pg_typeof(JSON('123')); +SELECT pg_typeof(JSON('123' RETURNING json)); +SELECT pg_typeof(JSON('123' RETURNING jsonb)); +SELECT pg_typeof(JSON('123' RETURNING pg_catalog.json)); + +SET sql_json = json; + +-- JSON_SCALAR() +SELECT JSON_SCALAR(); +SELECT JSON_SCALAR(NULL); +SELECT JSON_SCALAR(NULL::int); +SELECT JSON_SCALAR(123); +SELECT JSON_SCALAR(123.45); +SELECT JSON_SCALAR(123.45::numeric); +SELECT JSON_SCALAR(true); +SELECT JSON_SCALAR(false); +SELECT JSON_SCALAR(' 123.45'); +SELECT JSON_SCALAR('2020-06-07'::date); +SELECT JSON_SCALAR('2020-06-07 01:02:03'::timestamp); +SELECT JSON_SCALAR('{}'::json); +SELECT JSON_SCALAR('{}'::jsonb); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR('123'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); + +SET sql_json = jsonb; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING json); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING jsonb); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SCALAR(123 RETURNING pg_catalog.json); + +SET sql_json = json; + +-- JSON_SERIALIZE() +SELECT JSON_SERIALIZE(); +SELECT JSON_SERIALIZE(NULL); +SELECT JSON_SERIALIZE(JSON('{ "a" : 1 } ')); +SELECT JSON_SERIALIZE('{ "a" : 1 } '); +SELECT JSON_SERIALIZE('1'); +SELECT JSON_SERIALIZE('1' FORMAT JSON); +SELECT JSON_SERIALIZE('{ "a" : 1 } ' RETURNING bytea); +SELECT pg_typeof(JSON_SERIALIZE(NULL)); + +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT JSON_SERIALIZE('{}' RETURNING bytea); + +-- JSON_OBJECT() +SELECT JSON_OBJECT(); +SELECT JSON_OBJECT(RETURNING json); +SELECT JSON_OBJECT(RETURNING json FORMAT JSON); +SELECT JSON_OBJECT(RETURNING jsonb); +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); +SELECT JSON_OBJECT(RETURNING text); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +SELECT JSON_OBJECT(RETURNING bytea); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF16); +SELECT JSON_OBJECT(RETURNING bytea FORMAT JSON ENCODING UTF32); + +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::json FORMAT JSON ENCODING UTF8); +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON); +SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8); + +SELECT JSON_OBJECT(NULL: 1); +SELECT JSON_OBJECT('a': 2 + 3); +SELECT JSON_OBJECT('a' VALUE 2 + 3); +--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3); +SELECT JSON_OBJECT('a' || 2: 1); +SELECT JSON_OBJECT(('a' || 2) VALUE 1); +--SELECT JSON_OBJECT('a' || 2 VALUE 1); +--SELECT JSON_OBJECT(KEY 'a' || 2 VALUE 1); +SELECT JSON_OBJECT('a': 2::text); +SELECT JSON_OBJECT('a' VALUE 2::text); +--SELECT JSON_OBJECT(KEY 'a' VALUE 2::text); +SELECT JSON_OBJECT(1::text: 2); +SELECT JSON_OBJECT((1::text) VALUE 2); +--SELECT JSON_OBJECT(1::text VALUE 2); +--SELECT JSON_OBJECT(KEY 1::text VALUE 2); +SELECT JSON_OBJECT(json '[1]': 123); +SELECT JSON_OBJECT(ARRAY[1,2,3]: 'aaa'); + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' +); + +SELECT JSON_OBJECT( + 'a': '123', + 1.23: 123, + 'c': json '[ 1,true,{ } ]', + 'd': jsonb '{ "x" : 123.45 }' + RETURNING jsonb +); + +/* +SELECT JSON_OBJECT( + 'a': '123', + KEY 1.23 VALUE 123, + 'c' VALUE json '[1, true, {}]' +); +*/ + +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa')); +SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING jsonb)); + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text)); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea)); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON); + +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); +SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL); + +SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb); +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); + +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 NULL ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb); +SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WITH UNIQUE RETURNING jsonb); + + +-- JSON_ARRAY() +SELECT JSON_ARRAY(); +SELECT JSON_ARRAY(RETURNING json); +SELECT JSON_ARRAY(RETURNING json FORMAT JSON); +SELECT JSON_ARRAY(RETURNING jsonb); +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); +SELECT JSON_ARRAY(RETURNING text); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8); +SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING); +SELECT JSON_ARRAY(RETURNING bytea); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF8); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF16); +SELECT JSON_ARRAY(RETURNING bytea FORMAT JSON ENCODING UTF32); + +SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); + +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL); +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL); +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL); +SELECT JSON_ARRAY('a', NULL, 'b' NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY('a', NULL, 'b' ABSENT ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(NULL, NULL, 'b' ABSENT ON NULL RETURNING jsonb); + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text)); +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text)); +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON); + +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i)); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i)); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb); +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL); +--SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) NULL ON NULL RETURNING jsonb); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); +-- Should fail +SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); +SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); +SELECT JSON_ARRAY(SELECT * FROM (VALUES (1, 2)) foo(i, j)); + +-- JSON_ARRAYAGG() +SELECT JSON_ARRAYAGG(i) IS NULL, + JSON_ARRAYAGG(i RETURNING jsonb) IS NULL +FROM generate_series(1, 0) i; + +SELECT JSON_ARRAYAGG(i), + JSON_ARRAYAGG(i RETURNING jsonb) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(i ORDER BY i DESC) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(i::text::json) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(JSON_ARRAY(i, i + 1 RETURNING text) FORMAT JSON) +FROM generate_series(1, 5) i; + +SELECT JSON_ARRAYAGG(NULL), + JSON_ARRAYAGG(NULL RETURNING jsonb) +FROM generate_series(1, 5); + +SELECT JSON_ARRAYAGG(NULL NULL ON NULL), + JSON_ARRAYAGG(NULL NULL ON NULL RETURNING jsonb) +FROM generate_series(1, 5); + +SELECT + JSON_ARRAYAGG(bar), + JSON_ARRAYAGG(bar RETURNING jsonb), + JSON_ARRAYAGG(bar ABSENT ON NULL), + JSON_ARRAYAGG(bar ABSENT ON NULL RETURNING jsonb), + JSON_ARRAYAGG(bar NULL ON NULL), + JSON_ARRAYAGG(bar NULL ON NULL RETURNING jsonb), + JSON_ARRAYAGG(foo), + JSON_ARRAYAGG(foo RETURNING jsonb), + JSON_ARRAYAGG(foo ORDER BY bar) FILTER (WHERE bar > 2), + JSON_ARRAYAGG(foo ORDER BY bar RETURNING jsonb) FILTER (WHERE bar > 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL)) foo(bar); + +SELECT + bar, JSON_ARRAYAGG(bar) FILTER (WHERE bar > 2) OVER (PARTITION BY foo.bar % 2) +FROM + (VALUES (NULL), (3), (1), (NULL), (NULL), (5), (2), (4), (NULL), (5), (4)) foo(bar); + +-- JSON_OBJECTAGG() +SELECT JSON_OBJECTAGG('key': 1) IS NULL, + JSON_OBJECTAGG('key': 1 RETURNING jsonb) IS NULL +WHERE FALSE; + +SELECT JSON_OBJECTAGG(NULL: 1); + +SELECT JSON_OBJECTAGG(NULL: 1 RETURNING jsonb); + +SELECT + JSON_OBJECTAGG(i: i), +-- JSON_OBJECTAGG(i VALUE i), +-- JSON_OBJECTAGG(KEY i VALUE i), + JSON_OBJECTAGG(i: i RETURNING jsonb) +FROM + generate_series(1, 5) i; + +SELECT + JSON_OBJECTAGG(k: v), + JSON_OBJECTAGG(k: v NULL ON NULL), + JSON_OBJECTAGG(k: v ABSENT ON NULL), + JSON_OBJECTAGG(k: v RETURNING jsonb), + JSON_OBJECTAGG(k: v NULL ON NULL RETURNING jsonb), + JSON_OBJECTAGG(k: v ABSENT ON NULL RETURNING jsonb) +FROM + (VALUES (1, 1), (1, NULL), (2, NULL), (3, 3)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS) +FROM (VALUES (1, 1), (0, NULL), (3, NULL), (2, 2), (4, NULL)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb) +FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v); + +-- Test JSON_OBJECT deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + +CREATE VIEW json_object_view AS +SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json); + +\sv json_object_view + +DROP VIEW json_object_view; + +-- Test JSON_ARRAY deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + +CREATE VIEW json_array_view AS +SELECT JSON_ARRAY('1' FORMAT JSON, 2 RETURNING json); + +\sv json_array_view + +DROP VIEW json_array_view; + +-- Test JSON_OBJECTAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + +CREATE VIEW json_objectagg_view AS +SELECT JSON_OBJECTAGG(i: ('111' || i)::bytea FORMAT JSON WITH UNIQUE RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +\sv json_objectagg_view + +DROP VIEW json_objectagg_view; + +-- Test JSON_ARRAYAGG deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) OVER (PARTITION BY i % 2) +FROM generate_series(1,5) i; + +CREATE VIEW json_arrayagg_view AS +SELECT JSON_ARRAYAGG(('111' || i)::bytea FORMAT JSON NULL ON NULL RETURNING text) FILTER (WHERE i > 3) +FROM generate_series(1,5) i; + +\sv json_arrayagg_view + +DROP VIEW json_arrayagg_view; + +-- Test JSON_ARRAY(subquery) deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); + +CREATE VIEW json_array_subquery_view AS +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING jsonb); + +\sv json_array_subquery_view + +DROP VIEW json_array_subquery_view; + +-- IS JSON predicate +SELECT NULL IS JSON; +SELECT NULL IS NOT JSON; +SELECT NULL::json IS JSON; +SELECT NULL::jsonb IS JSON; +SELECT NULL::text IS JSON; +SELECT NULL::bytea IS JSON; +SELECT NULL::int IS JSON; + +SELECT '' IS JSON; + +SELECT bytea '\x00' IS JSON; + +CREATE TABLE test_is_json (js text); + +INSERT INTO test_is_json VALUES + (NULL), + (''), + ('123'), + ('"aaa "'), + ('true'), + ('null'), + ('[]'), + ('[1, "2", {}]'), + ('{}'), + ('{ "a": 1, "b": null }'), + ('{ "a": 1, "a": null }'), + ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'), + ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'), + ('aaa'), + ('{a:1}'), + ('["a",]'); + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + test_is_json; + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js); + +SELECT + js0, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js); + +SELECT + js, + js IS JSON "IS JSON", + js IS NOT JSON "IS NOT JSON", + js IS JSON VALUE "IS VALUE", + js IS JSON OBJECT "IS OBJECT", + js IS JSON ARRAY "IS ARRAY", + js IS JSON SCALAR "IS SCALAR", + js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js); + +-- Test IS JSON deparsing +EXPLAIN (VERBOSE, COSTS OFF) +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + +CREATE VIEW is_json_view AS +SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i; + +\sv is_json_view + +DROP VIEW is_json_view; -- 2.25.4