From 59751b87a0d25979393b4d1c62818d87021e018d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 14 Jun 2019 15:24:54 +0300 Subject: [PATCH 01/66] Implement like_regex flag 'q' in jsonpath --- src/backend/utils/adt/jsonpath.c | 2 ++ src/backend/utils/adt/jsonpath_exec.c | 6 ++++ src/backend/utils/adt/jsonpath_gram.y | 8 +++++ src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 36 ++++++++++++++++++++ src/test/regress/expected/jsonpath.out | 18 ++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 6 ++++ src/test/regress/sql/jsonpath.sql | 3 ++ 8 files changed, 80 insertions(+) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d5da155867..87ae60e490 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -563,6 +563,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, appendStringInfoChar(buf, 'm'); if (v->content.like_regex.flags & JSP_REGEX_WSPACE) appendStringInfoChar(buf, 'x'); + if (v->content.like_regex.flags & JSP_REGEX_QUOTE) + appendStringInfoChar(buf, 'q'); appendStringInfoChar(buf, '"'); } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 873d64b630..bef911232e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1664,6 +1664,12 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, cxt->cflags &= ~REG_NEWLINE; if (flags & JSP_REGEX_WSPACE) cxt->cflags |= REG_EXPANDED; + if ((flags & JSP_REGEX_QUOTE) && + !(flags & (JSP_REGEX_MLINE | JSP_REGEX_SLINE | JSP_REGEX_WSPACE))) + { + cxt->cflags &= ~REG_ADVANCED; + cxt->cflags |= REG_QUOTE; + } } if (RE_compile_and_execute(cxt->regex, str->val.string.val, diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 22c2089f78..a0a930ccf0 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -510,6 +510,14 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, v->value.like_regex.flags |= JSP_REGEX_WSPACE; cflags |= REG_EXPANDED; break; + case 'q': + v->value.like_regex.flags |= JSP_REGEX_QUOTE; + if (!(v->value.like_regex.flags & (JSP_REGEX_MLINE | JSP_REGEX_SLINE | JSP_REGEX_WSPACE))) + { + cflags &= ~REG_ADVANCED; + cflags |= REG_QUOTE; + } + break; default: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 3e9d60cb76..40ad5fda92 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -91,6 +91,7 @@ typedef enum JsonPathItemType #define JSP_REGEX_SLINE 0x02 /* s flag, single-line mode */ #define JSP_REGEX_MLINE 0x04 /* m flag, multi-line mode */ #define JSP_REGEX_WSPACE 0x08 /* x flag, expanded syntax */ +#define JSP_REGEX_QUOTE 0x10 /* q flag, no special characters */ /* * Support functions to parse/construct binary value. diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index b486fb602a..31a871af02 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1622,6 +1622,42 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", " "abdacb" (2 rows) +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")'); + jsonb_path_query +------------------ + "a\\b" + "^a\\b$" +(2 rows) + +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")'); + jsonb_path_query +------------------ + "a\b" +(1 row) + +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")'); + jsonb_path_query +------------------ + "^a\\b$" +(1 row) + +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")'); + jsonb_path_query +------------------ + "^a\\b$" +(1 row) + +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")'); + jsonb_path_query +------------------ + "a\b" +(1 row) + -- jsonpath operators SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); jsonb_path_query diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 0f9cd17e2e..ecdd453942 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -453,6 +453,24 @@ select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath; $?(@ like_regex "pattern" flag "sx") (1 row) +select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath; + jsonpath +------------------------------------- + $?(@ like_regex "pattern" flag "q") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath; + jsonpath +-------------------------------------- + $?(@ like_regex "pattern" flag "iq") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath; + jsonpath +---------------------------------------- + $?(@ like_regex "pattern" flag "imxq") +(1 row) + select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; ERROR: invalid input syntax for type jsonpath LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 464ff94be3..733fbd4e0d 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -339,6 +339,12 @@ select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", " select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")'); select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")'); select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "q")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "a\\b" flag "")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "q")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "q")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")'); +select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")'); -- jsonpath operators diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 9171ddbc6c..29ea77a485 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -83,6 +83,9 @@ select '$ ? (@ like_regex "pattern" flag "i")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "is")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath; +select '$ ? (@ like_regex "pattern" flag "q")'::jsonpath; +select '$ ? (@ like_regex "pattern" flag "iq")'::jsonpath; +select '$ ? (@ like_regex "pattern" flag "smixq")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; select '$ < 1'::jsonpath; From b4c60a75006481ededf10d64f8d42feac754159d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 22 Mar 2019 15:15:38 +0300 Subject: [PATCH 02/66] Fix parsing of unquoted identifiers in jsonpath --- src/backend/utils/adt/jsonpath_scan.l | 80 ++++++++------------------ src/test/regress/expected/jsonpath.out | 14 ++++- src/test/regress/sql/jsonpath.sql | 3 + 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 2165ffcc25..feb282ea88 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -63,22 +63,23 @@ fprintf_to_ereport(const char *fmt, const char *msg) * quoted variable names and C-tyle comments. * Exclusive states: * - quoted strings - * - non-quoted strings * - quoted variable names * - single-quoted strings * - C-style comment */ %x xq -%x xnq %x xvq %x xsq %x xc -special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/] -any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f] +special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\~\`\;] +id_start [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\~\`\;\"\' \t\n\r\f(0-9)] blank [ \t\n\r\f] +id_rest ({id_start}|[0-9]) +id {id_start}{id_rest}* + digit [0-9] integer (0|[1-9]{digit}*) decimal {integer}\.{digit}+ @@ -95,66 +96,37 @@ hex_fail \\x{hex_dig}{0,1} %% -{any}+ { - addstring(false, yytext, yyleng); - } +\\[\"\'\\] { addchar(false, yytext[1]); } -{blank}+ { - yylval->str = scanstring; - BEGIN INITIAL; - return checkKeyword(); - } +\\b { addchar(false, '\b'); } +\\f { addchar(false, '\f'); } -\/\* { - yylval->str = scanstring; - BEGIN xc; - } +\\n { addchar(false, '\n'); } -({special}|\"|\') { - yylval->str = scanstring; - yyless(0); - BEGIN INITIAL; - return checkKeyword(); - } +\\r { addchar(false, '\r'); } -<> { - yylval->str = scanstring; - BEGIN INITIAL; - return checkKeyword(); - } - -\\[\"\'\\] { addchar(false, yytext[1]); } - -\\b { addchar(false, '\b'); } - -\\f { addchar(false, '\f'); } - -\\n { addchar(false, '\n'); } - -\\r { addchar(false, '\r'); } +\\t { addchar(false, '\t'); } -\\t { addchar(false, '\t'); } +\\v { addchar(false, '\v'); } -\\v { addchar(false, '\v'); } +{unicode}+ { parseUnicode(yytext, yyleng); } -{unicode}+ { parseUnicode(yytext, yyleng); } +{hex_char} { parseHexChar(yytext); } -{hex_char} { parseHexChar(yytext); } +{unicode}*{unicodefail} { yyerror(NULL, "invalid unicode sequence"); } -{unicode}*{unicodefail} { yyerror(NULL, "invalid unicode sequence"); } +{hex_fail} { yyerror(NULL, "invalid hex character sequence"); } -{hex_fail} { yyerror(NULL, "invalid hex character sequence"); } - -{unicode}+\\ { +{unicode}+\\ { /* throw back the \\, and treat as unicode */ yyless(yyleng - 1); parseUnicode(yytext, yyleng); } -\\. { yyerror(NULL, "escape sequence is invalid"); } +\\. { yyerror(NULL, "escape sequence is invalid"); } -\\ { yyerror(NULL, "unexpected end after backslash"); } +\\ { yyerror(NULL, "unexpected end after backslash"); } <> { yyerror(NULL, "unexpected end of quoted string"); } @@ -210,7 +182,7 @@ hex_fail \\x{hex_dig}{0,1} \> { return GREATER_P; } -\${any}+ { +\${id} { addstring(true, yytext + 1, yyleng - 1); addchar(false, '\0'); yylval->str = scanstring; @@ -263,9 +235,11 @@ hex_fail \\x{hex_dig}{0,1} ({realfail1}|{realfail2}) { yyerror(NULL, "invalid floating point number"); } -{any}+ { +{id} { addstring(true, yytext, yyleng); - BEGIN xnq; + addchar(false, '\0'); + yylval->str = scanstring; + return checkKeyword(); } \" { @@ -278,12 +252,6 @@ hex_fail \\x{hex_dig}{0,1} BEGIN xsq; } -\\ { - yyless(0); - addchar(true, '\0'); - BEGIN xnq; - } - <> { yyterminate(); } %% diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index ecdd453942..51a6b7ac75 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -190,6 +190,10 @@ select '''\x50\u0067\u{53}\u{051}\u{00004C}'''::jsonpath; (1 row) select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath; +ERROR: syntax error, unexpected $undefined, expecting $end at or near "\" of jsonpath input +LINE 1: select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::json... + ^ +select '$."foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar"'::jsonpath; jsonpath --------------------- $."fooPgSQL\t\"bar" @@ -817,9 +821,17 @@ select '0'::jsonpath; (1 row) select '00'::jsonpath; -ERROR: syntax error, unexpected IDENT_P at end of jsonpath input +ERROR: syntax error, unexpected INT_P, expecting $end at or near "0" of jsonpath input LINE 1: select '00'::jsonpath; ^ +select '$.00'::jsonpath; +ERROR: syntax error, unexpected INT_P at or near "0" of jsonpath input +LINE 1: select '$.00'::jsonpath; + ^ +select '$.0a'::jsonpath; +ERROR: syntax error, unexpected INT_P at or near "0" of jsonpath input +LINE 1: select '$.0a'::jsonpath; + ^ select '0.0'::jsonpath; jsonpath ---------- diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 29ea77a485..7dbfc3bfcf 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -34,6 +34,7 @@ select '''\b\f\r\n\t\v\"\''\\'''::jsonpath; select '"\x50\u0067\u{53}\u{051}\u{00004C}"'::jsonpath; select '''\x50\u0067\u{53}\u{051}\u{00004C}'''::jsonpath; select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath; +select '$."foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar"'::jsonpath; select '$.g ? ($.a == 1)'::jsonpath; select '$.g ? (@ == 1)'::jsonpath; @@ -153,6 +154,8 @@ select '$ ? (@.a < +10.1e+1)'::jsonpath; select '0'::jsonpath; select '00'::jsonpath; +select '$.00'::jsonpath; +select '$.0a'::jsonpath; select '0.0'::jsonpath; select '0.000'::jsonpath; select '0.000e1'::jsonpath; From 9ee24e54dc4be085338fbfcb060acad2a1212445 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 21 Mar 2019 22:54:40 +0300 Subject: [PATCH 03/66] Remove accidental backslash --- src/backend/utils/adt/jsonpath_exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index bef911232e..2a5f9a2386 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -603,7 +603,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperError; ereport(ERROR, - (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), \ + (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), errmsg("JSON object does not contain key \"%s\"", pnstrdup(key.val.string.val, key.val.string.len)))); From c9e704408e4c822b5a01cec4c9c38bc25a48d0f9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 29 Jan 2019 11:23:40 +0300 Subject: [PATCH 04/66] Preliminary datetime infrastructure --- doc/src/sgml/func.sgml | 24 ++ src/backend/utils/adt/date.c | 11 +- src/backend/utils/adt/formatting.c | 407 ++++++++++++++++++++-- src/backend/utils/adt/timestamp.c | 3 +- src/include/utils/date.h | 3 + src/include/utils/datetime.h | 2 + src/include/utils/formatting.h | 3 + src/test/regress/expected/horology.out | 79 +++++ src/test/regress/expected/timestamp.out | 15 + src/test/regress/expected/timestamptz.out | 15 + src/test/regress/sql/horology.sql | 9 + src/test/regress/sql/timestamp.sql | 8 + src/test/regress/sql/timestamptz.sql | 8 + 13 files changed, 553 insertions(+), 34 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e918133874..b3b145788d 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -6146,6 +6146,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); US microsecond (000000-999999) + + FF1 + decisecond (0-9) + + + FF2 + centisecond (00-99) + + + FF3 + millisecond (000-999) + + + FF4 + tenth of a millisecond (0000-9999) + + + FF5 + hundredth of a millisecond (00000-99999) + + + FF6 + microsecond (000000-999999) + SSSS seconds past midnight (0-86399) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 1ff3cfea8b..4005f6b26d 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -41,11 +41,6 @@ #endif -static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result); -static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result); -static void AdjustTimeForTypmod(TimeADT *time, int32 typmod); - - /* common code for timetypmodin and timetztypmodin */ static int32 anytime_typmodin(bool istz, ArrayType *ta) @@ -1211,7 +1206,7 @@ time_in(PG_FUNCTION_ARGS) /* tm2time() * Convert a tm structure to a time data type. */ -static int +int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result) { *result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) @@ -1387,7 +1382,7 @@ time_scale(PG_FUNCTION_ARGS) * have a fundamental tie together but rather a coincidence of * implementation. - thomas */ -static void +void AdjustTimeForTypmod(TimeADT *time, int32 typmod) { static const int64 TimeScales[MAX_TIME_PRECISION + 1] = { @@ -1965,7 +1960,7 @@ time_part(PG_FUNCTION_ARGS) /* tm2timetz() * Convert a tm structure to a time data type. */ -static int +int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result) { result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) * diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 206576d4bd..7c19f33e3f 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -86,6 +86,7 @@ #endif #include "catalog/pg_collation.h" +#include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/date.h" @@ -436,7 +437,8 @@ typedef struct clock, /* 12 or 24 hour clock? */ tzsign, /* +1, -1 or 0 if timezone info is absent */ tzh, - tzm; + tzm, + ff; /* fractional precision */ } TmFromChar; #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar)) @@ -596,6 +598,12 @@ typedef enum DCH_Day, DCH_Dy, DCH_D, + DCH_FF1, + DCH_FF2, + DCH_FF3, + DCH_FF4, + DCH_FF5, + DCH_FF6, DCH_FX, /* global suffix */ DCH_HH24, DCH_HH12, @@ -645,6 +653,12 @@ typedef enum DCH_dd, DCH_dy, DCH_d, + DCH_ff1, + DCH_ff2, + DCH_ff3, + DCH_ff4, + DCH_ff5, + DCH_ff6, DCH_fx, DCH_hh24, DCH_hh12, @@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = { {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE}, {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE}, {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, - {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */ + {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */ + {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */ {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, @@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = { {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN}, {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE}, {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, - {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */ + {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */ + {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */ {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, @@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1, - DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF, + DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF, DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY, -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc, - DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, + DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww, -1, DCH_y_yyy, -1, -1, -1, -1 @@ -962,6 +988,10 @@ 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 @@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, static void DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid); -static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out); +static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out, + bool strict); #ifdef DEBUG_TO_FROM_CHAR static void dump_index(const KeyWord *k, const int *index); @@ -994,8 +1025,9 @@ static int from_char_parse_int_len(int *dest, char **src, const int len, FormatN static int from_char_parse_int(int *dest, char **src, FormatNode *node); static int seq_search(char *name, const char *const *array, int type, int max, int *len); static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node); -static void do_to_timestamp(text *date_txt, text *fmt, - struct pg_tm *tm, fsec_t *fsec); +static void do_to_timestamp(text *date_txt, text *fmt, bool strict, + struct pg_tm *tm, fsec_t *fsec, int *fprec, + int *flags); static char *fill_str(char *str, int c, int max); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); static char *int_to_roman(int number); @@ -2517,18 +2549,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s); break; - case DCH_MS: /* millisecond */ - sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000))); - if (S_THth(n->suffix)) - str_numth(s, s, S_TH_TYPE(n->suffix)); +#define DCH_to_char_fsec(frac_fmt, frac_val) \ + sprintf(s, frac_fmt, (int) (frac_val)); \ + if (S_THth(n->suffix)) \ + str_numth(s, s, S_TH_TYPE(n->suffix)); \ s += strlen(s); + case DCH_FF1: /* decisecond */ + DCH_to_char_fsec("%01d", in->fsec / 100000); + break; + case DCH_FF2: /* centisecond */ + DCH_to_char_fsec("%02d", in->fsec / 10000); + break; + case DCH_FF3: + case DCH_MS: /* millisecond */ + DCH_to_char_fsec("%03d", in->fsec / 1000); + break; + case DCH_FF4: + DCH_to_char_fsec("%04d", in->fsec / 100); break; + case DCH_FF5: + DCH_to_char_fsec("%05d", in->fsec / 10); + break; + case DCH_FF6: case DCH_US: /* microsecond */ - sprintf(s, "%06d", (int) in->fsec); - if (S_THth(n->suffix)) - str_numth(s, s, S_TH_TYPE(n->suffix)); - s += strlen(s); + DCH_to_char_fsec("%06d", in->fsec); break; +#undef DCH_to_char_fsec case DCH_SSSS: sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR + tm->tm_min * SECS_PER_MINUTE + @@ -3010,13 +3056,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col /* ---------- * Process a string as denoted by a list of FormatNodes. * The TmFromChar struct pointed to by 'out' is populated with the results. + * 'strict' enables error reporting on unmatched trailing characters in input or + * format strings patterns. * * Note: we currently don't have any to_interval() function, so there * is no need here for INVALID_FOR_INTERVAL checks. * ---------- */ static void -DCH_from_char(FormatNode *node, char *in, TmFromChar *out) +DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) { FormatNode *n; char *s; @@ -3153,8 +3201,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out) SKIP_THth(s, n->suffix); break; + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + out->ff = n->key->id - DCH_FF1 + 1; + /* fall through */ case DCH_US: /* microsecond */ - len = from_char_parse_int_len(&out->us, &s, 6, n); + len = from_char_parse_int_len(&out->us, &s, + n->key->id == DCH_US ? 6 : + out->ff, n); out->us *= len == 1 ? 100000 : len == 2 ? 10000 : @@ -3380,6 +3438,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out) } } } + + if (strict) + { + if (n->type != NODE_TYPE_END) + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("input string is too short for datetime format"))); + + while (*s != '\0' && isspace((unsigned char) *s)) + s++; + + if (*s != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string after " + "datetime format"))); + } } /* @@ -3400,6 +3475,109 @@ DCH_prevent_counter_overflow(void) } } +/* Get mask of date/time/zone components present in format nodes. */ +static int +DCH_datetime_type(FormatNode *node) +{ + FormatNode *n; + int flags = 0; + + for (n = node; n->type != NODE_TYPE_END; n++) + { + if (n->type != NODE_TYPE_ACTION) + continue; + + switch (n->key->id) + { + case DCH_FX: + break; + case DCH_A_M: + case DCH_P_M: + case DCH_a_m: + case DCH_p_m: + case DCH_AM: + case DCH_PM: + case DCH_am: + case DCH_pm: + case DCH_HH: + case DCH_HH12: + case DCH_HH24: + case DCH_MI: + case DCH_SS: + case DCH_MS: /* millisecond */ + case DCH_US: /* microsecond */ + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + case DCH_SSSS: + flags |= DCH_TIMED; + break; + case DCH_tz: + case DCH_TZ: + case DCH_OF: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name))); + flags |= DCH_ZONED; + break; + case DCH_TZH: + case DCH_TZM: + flags |= DCH_ZONED; + break; + case DCH_A_D: + case DCH_B_C: + case DCH_a_d: + case DCH_b_c: + case DCH_AD: + case DCH_BC: + case DCH_ad: + case DCH_bc: + case DCH_MONTH: + case DCH_Month: + case DCH_month: + case DCH_MON: + case DCH_Mon: + case DCH_mon: + case DCH_MM: + case DCH_DAY: + case DCH_Day: + case DCH_day: + case DCH_DY: + case DCH_Dy: + case DCH_dy: + case DCH_DDD: + case DCH_IDDD: + case DCH_DD: + case DCH_D: + case DCH_ID: + case DCH_WW: + case DCH_Q: + case DCH_CC: + case DCH_Y_YYY: + case DCH_YYYY: + case DCH_IYYY: + case DCH_YYY: + case DCH_IYY: + case DCH_YY: + case DCH_IY: + case DCH_Y: + case DCH_I: + case DCH_RM: + case DCH_rm: + case DCH_W: + case DCH_J: + flags |= DCH_DATED; + break; + } + } + + return flags; +} + /* select a DCHCacheEntry to hold the given format picture */ static DCHCacheEntry * DCH_cache_getnew(const char *str) @@ -3688,8 +3866,9 @@ to_timestamp(PG_FUNCTION_ARGS) int tz; struct pg_tm tm; fsec_t fsec; + int fprec; - do_to_timestamp(date_txt, fmt, &tm, &fsec); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3707,6 +3886,10 @@ to_timestamp(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + /* Use the specified fractional precision, if any. */ + if (fprec) + AdjustTimestampForTypmod(&result, fprec); + PG_RETURN_TIMESTAMP(result); } @@ -3724,7 +3907,7 @@ to_date(PG_FUNCTION_ARGS) struct pg_tm tm; fsec_t fsec; - do_to_timestamp(date_txt, fmt, &tm, &fsec); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3745,11 +3928,176 @@ to_date(PG_FUNCTION_ARGS) PG_RETURN_DATEADT(result); } +/* + * Make datetime type from 'date_txt' which is formated at argument 'fmt'. + * Actual datatype (returned in 'typid', 'typmod') is determined by + * presence of date/time/zone components in the format string. + */ +Datum +to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid, + int32 *typmod, int *tz) +{ + struct pg_tm tm; + fsec_t fsec; + int fprec = 0; + int flags; + + do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags); + + *typmod = fprec ? fprec : -1; /* fractional part precision */ + *tz = 0; + + if (flags & DCH_DATED) + { + if (flags & DCH_TIMED) + { + if (flags & DCH_ZONED) + { + TimestampTz result; + + if (tm.tm_zone) + tzname = (char *) tm.tm_zone; + + if (tzname) + { + int dterr = DecodeTimezone(tzname, tz); + + if (dterr) + DateTimeParseError(dterr, tzname, "timestamptz"); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time-zone in timestamptz input string"))); + + *tz = DetermineTimeZoneOffset(&tm, session_timezone); + } + + if (tm2timestamp(&tm, fsec, tz, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamptz out of range"))); + + AdjustTimestampForTypmod(&result, *typmod); + + *typid = TIMESTAMPTZOID; + return TimestampTzGetDatum(result); + } + else + { + Timestamp result; + + if (tm2timestamp(&tm, fsec, NULL, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + AdjustTimestampForTypmod(&result, *typmod); + + *typid = TIMESTAMPOID; + return TimestampGetDatum(result); + } + } + else + { + if (flags & DCH_ZONED) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is zoned but not timed"))); + } + else + { + DateADT result; + + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - + POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt)))); + + *typid = DATEOID; + return DateADTGetDatum(result); + } + } + } + else if (flags & DCH_TIMED) + { + if (flags & DCH_ZONED) + { + TimeTzADT *result = palloc(sizeof(TimeTzADT)); + + if (tm.tm_zone) + tzname = (char *) tm.tm_zone; + + if (tzname) + { + int dterr = DecodeTimezone(tzname, tz); + + if (dterr) + DateTimeParseError(dterr, tzname, "timetz"); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time-zone in timestamptz input string"))); + + *tz = DetermineTimeZoneOffset(&tm, session_timezone); + } + + if (tm2timetz(&tm, fsec, *tz, result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timetz out of range"))); + + AdjustTimeForTypmod(&result->time, *typmod); + + *typid = TIMETZOID; + return TimeTzADTPGetDatum(result); + } + else + { + TimeADT result; + + if (tm2time(&tm, fsec, &result) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range"))); + + AdjustTimeForTypmod(&result, *typmod); + + *typid = TIMEOID; + return TimeADTGetDatum(result); + } + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is not dated and not timed"))); + } + + return (Datum) 0; +} + /* * do_to_timestamp: shared code for to_timestamp and to_date * * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm - * and fractional seconds. + * and fractional seconds and fractional precision. * * We parse 'fmt' into a list of FormatNodes, which is then passed to * DCH_from_char to populate a TmFromChar with the parsed contents of @@ -3757,10 +4105,15 @@ to_date(PG_FUNCTION_ARGS) * * The TmFromChar is then analysed and converted into the final results in * struct 'tm' and 'fsec'. + * + * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'. + * + * 'strict' enables error reporting on unmatched trailing characters in input or + * format strings patterns. */ static void -do_to_timestamp(text *date_txt, text *fmt, - struct pg_tm *tm, fsec_t *fsec) +do_to_timestamp(text *date_txt, text *fmt, bool strict, + struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags) { FormatNode *format; TmFromChar tmfc; @@ -3813,9 +4166,13 @@ do_to_timestamp(text *date_txt, text *fmt, /* dump_index(DCH_keywords, DCH_index); */ #endif - DCH_from_char(format, date_str, &tmfc); + DCH_from_char(format, date_str, &tmfc, strict); pfree(fmt_str); + + if (flags) + *flags = DCH_datetime_type(format); + if (!incache) pfree(format); } @@ -3997,6 +4354,8 @@ do_to_timestamp(text *date_txt, text *fmt, *fsec += tmfc.ms * 1000; if (tmfc.us) *fsec += tmfc.us; + if (fprec) + *fprec = tmfc.ff; /* fractional precision, if specified */ /* Range-check date fields according to bit mask computed above */ if (fmask != 0) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index e5ac371fa0..4d00e996b2 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -70,7 +70,6 @@ typedef struct static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec); static Timestamp dt2local(Timestamp dt, int timezone); -static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); static void AdjustIntervalForTypmod(Interval *interval, int32 typmod); static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); @@ -341,7 +340,7 @@ timestamp_scale(PG_FUNCTION_ARGS) * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod * Works for either timestamp or timestamptz. */ -static void +void AdjustTimestampForTypmod(Timestamp *time, int32 typmod) { static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { diff --git a/src/include/utils/date.h b/src/include/utils/date.h index bec129aff1..bd15bfa5bb 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod); extern TimeADT GetSQLLocalTime(int32 typmod); extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec); extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp); +extern int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result); +extern int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result); +extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod); #endif /* DATE_H */ diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 4de78ebe36..3c04e21b79 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n); extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); +extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); + #endif /* DATETIME_H */ diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 5b275dc985..227779cf79 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes); extern char *asc_toupper(const char *buff, size_t nbytes); extern char *asc_initcap(const char *buff, size_t nbytes); +extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname, + bool strict, Oid *typid, int32 *typmod, int *tz); + #endif diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index b2b4577333..74ecb7c10e 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM'); Sun Dec 18 03:18:00 2011 PST (1 row) +SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------ + 1 | Fri Nov 02 12:34:56 2018 PDT + 2 | Fri Nov 02 12:34:56 2018 PDT + 3 | Fri Nov 02 12:34:56 2018 PDT + 4 | Fri Nov 02 12:34:56 2018 PDT + 5 | Fri Nov 02 12:34:56 2018 PDT + 6 | Fri Nov 02 12:34:56 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+-------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.1 2018 PDT + 3 | Fri Nov 02 12:34:56.1 2018 PDT + 4 | Fri Nov 02 12:34:56.1 2018 PDT + 5 | Fri Nov 02 12:34:56.1 2018 PDT + 6 | Fri Nov 02 12:34:56.1 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+--------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.12 2018 PDT + 4 | Fri Nov 02 12:34:56.12 2018 PDT + 5 | Fri Nov 02 12:34:56.12 2018 PDT + 6 | Fri Nov 02 12:34:56.12 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+---------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.123 2018 PDT + 5 | Fri Nov 02 12:34:56.123 2018 PDT + 6 | Fri Nov 02 12:34:56.123 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+----------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1234 2018 PDT + 5 | Fri Nov 02 12:34:56.1234 2018 PDT + 6 | Fri Nov 02 12:34:56.1234 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------------ + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1235 2018 PDT + 5 | Fri Nov 02 12:34:56.12345 2018 PDT + 6 | Fri Nov 02 12:34:56.12345 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1235 2018 PDT + 5 | Fri Nov 02 12:34:56.12346 2018 PDT + 6 | Fri Nov 02 12:34:56.123456 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789" -- -- Check handling of multiple spaces in format and/or input -- diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index b2b171f560..6bebb4a110 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') | 2001 1 1 1 1 1 1 (65 rows) +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamp), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + to_char_12 | to_char +------------+-------------------------------------------------------------------- + | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000 + | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000 + | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010 + | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012 +(4 rows) + -- timestamp numeric fields constructor SELECT make_timestamp(2014,12,28,6,30,45.887); make_timestamp diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 8a4c719993..cdd3c1401e 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') | 2001 1 1 1 1 1 1 (66 rows) +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamptz), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + to_char_12 | to_char +------------+-------------------------------------------------------------------- + | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000 + | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000 + | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010 + | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012 +(4 rows) + -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours SET timezone = '00:00'; SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM"; diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index e356dd563e..3c8580397a 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM'); +SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + -- -- Check handling of multiple spaces in format and/or input -- diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 150eb54c87..dcc5ff61f3 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID') SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') FROM TIMESTAMP_TBL; +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamp), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + -- timestamp numeric fields constructor SELECT make_timestamp(2014,12,28,6,30,45.887); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index c3bd46c233..588c3e033f 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID') SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') FROM TIMESTAMPTZ_TBL; +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamptz), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours SET timezone = '00:00'; SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM"; From e58ec8bd5ebef3e647e19419b85f444f4fc03467 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 18:43:19 +0300 Subject: [PATCH 05/66] Add jsonpath .datetime() item method --- doc/src/sgml/func.sgml | 25 +- src/backend/utils/adt/date.c | 48 +- src/backend/utils/adt/formatting.c | 741 +++++++++++-------- src/backend/utils/adt/json.c | 32 +- src/backend/utils/adt/jsonb.c | 27 +- src/backend/utils/adt/jsonb_util.c | 20 + src/backend/utils/adt/jsonpath.c | 23 + src/backend/utils/adt/jsonpath_exec.c | 470 ++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 16 + src/backend/utils/adt/jsonpath_scan.l | 1 + src/backend/utils/adt/timestamp.c | 58 +- src/backend/utils/errcodes.txt | 1 + src/include/utils/date.h | 3 + src/include/utils/datetime.h | 2 + src/include/utils/formatting.h | 4 +- src/include/utils/jsonapi.h | 3 +- src/include/utils/jsonb.h | 23 +- src/include/utils/jsonpath.h | 1 + src/include/utils/timestamp.h | 3 + src/test/regress/expected/jsonb_jsonpath.out | 545 ++++++++++++++ src/test/regress/expected/jsonpath.out | 18 + src/test/regress/sql/jsonb_jsonpath.sql | 148 ++++ src/test/regress/sql/jsonpath.sql | 3 + 23 files changed, 1870 insertions(+), 345 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b3b145788d..eaf367a5db 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11650,9 +11650,9 @@ table2-mapping listed in . Each method must be preceded by a dot, while arithmetic and boolean operators are separated from the operands by spaces. For example, - you can get an array size: + you can convert a text string into a datetime value: -'$.track.segments.size()' +'$.track.segments[*]."start time".datetime()' For more examples of using jsonpath operators and methods within path expressions, see @@ -11946,6 +11946,27 @@ table2-mapping $.z.abs() 0.3 + + datetime() + Datetime value converted from a string + ["2015-8-1", "2015-08-12"] + $[*] ? (@.datetime() < "2015-08-2". datetime()) + 2015-8-1 + + + datetime(template) + Datetime value converted from a string with a specified template + ["12:30", "18:40"] + $[*].datetime("HH24:MI") + "12:30:00", "18:40:00" + + + datetime(template, default_tz) + Datetime value converted from a string with a specified template and default timezone + ["12:30 -02", "18:40"] + $[*].datetime("HH24:MI TZH", "+03:00") + "12:30:00-02:00", "18:40:00+03:00" + keyvalue() diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 4005f6b26d..4a24e8771d 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -40,6 +40,15 @@ #error -ffast-math is known to break this code #endif +#define date_ereport(res, msg) do { \ + if (error) \ + { \ + *error = true; \ + return (res); \ + } \ + else \ + ereport(ERROR, msg); \ +} while (0) /* common code for timetypmodin and timetztypmodin */ static int32 @@ -562,9 +571,8 @@ date_mii(PG_FUNCTION_ARGS) * Internal routines for promoting date to timestamp and timestamp with * time zone */ - -static Timestamp -date2timestamp(DateADT dateVal) +Timestamp +date2timestamp_internal(DateADT dateVal, bool *error) { Timestamp result; @@ -580,9 +588,9 @@ date2timestamp(DateADT dateVal) * boundary need be checked for overflow. */ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + date_ereport(0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); /* date is days since 2000, timestamp is microseconds since same... */ result = dateVal * USECS_PER_DAY; @@ -592,7 +600,13 @@ date2timestamp(DateADT dateVal) } static TimestampTz -date2timestamptz(DateADT dateVal) +date2timestamp(DateADT dateVal) +{ + return date2timestamp_internal(dateVal, NULL); +} + +TimestampTz +date2timestamptz_internal(DateADT dateVal, int *tzp, bool *error) { TimestampTz result; struct pg_tm tt, @@ -611,16 +625,16 @@ date2timestamptz(DateADT dateVal) * boundary need be checked for overflow. */ if (dateVal >= (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + date_ereport(0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); j2date(dateVal + POSTGRES_EPOCH_JDATE, &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; - tz = DetermineTimeZoneOffset(tm, session_timezone); + tz = tzp ? *tzp : DetermineTimeZoneOffset(tm, session_timezone); result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC; @@ -629,14 +643,20 @@ date2timestamptz(DateADT dateVal) * of time zone, check for allowed timestamp range after adding tz. */ if (!IS_VALID_TIMESTAMP(result)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + date_ereport(0, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); } return result; } +static TimestampTz +date2timestamptz(DateADT dateVal) +{ + return date2timestamptz_internal(dateVal, NULL, NULL); +} + /* * date2timestamp_no_overflow * diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 7c19f33e3f..b1ca4d114f 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -993,6 +993,15 @@ typedef struct NUMProc #define DCH_TIMED 0x02 #define DCH_ZONED 0x04 +#define dch_ereport(res, ...) do { \ + if (error) { \ + *error = true; \ + return (res); \ + } else { \ + ereport(ERROR, (__VA_ARGS__)); \ + } \ +} while (0) + /* ---------- * Functions * ---------- @@ -1007,8 +1016,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, static void DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid); -static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out, - bool strict); +static bool DCH_from_char(FormatNode *node, char *in, TmFromChar *out, + bool strict, bool *error); #ifdef DEBUG_TO_FROM_CHAR static void dump_index(const KeyWord *k, const int *index); @@ -1019,15 +1028,18 @@ static const char *get_th(char *num, int type); static char *str_numth(char *dest, char *num, int type); static int adjust_partial_year_to_2020(int year); static int strspace_len(char *str); -static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode); -static void from_char_set_int(int *dest, const int value, const FormatNode *node); -static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node); -static int from_char_parse_int(int *dest, char **src, FormatNode *node); +static bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, + bool *error); +static bool from_char_set_int(int *dest, const int value, const FormatNode *node, bool *error); +static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node, bool *error); +static int from_char_parse_int(int *dest, char **src, FormatNode *node, bool *error); static int seq_search(char *name, const char *const *array, int type, int max, int *len); -static int from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, FormatNode *node); -static void do_to_timestamp(text *date_txt, text *fmt, bool strict, +static int from_char_seq_search(int *dest, char **src, const char *const *array, + int type, int max, FormatNode *node, + bool *error); +static bool do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm, fsec_t *fsec, int *fprec, - int *flags); + int *flags, bool *error); static char *fill_str(char *str, int c, int max); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); static char *int_to_roman(int number); @@ -2203,21 +2215,26 @@ strspace_len(char *str) * * Puke if the date mode has already been set, and the caller attempts to set * it to a conflicting mode. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and false + * is returned. */ -static void -from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode) +static bool +from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode, bool *error) { if (mode != FROM_CHAR_DATE_NONE) { if (tmfc->mode == FROM_CHAR_DATE_NONE) tmfc->mode = mode; else if (tmfc->mode != mode) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid combination of date conventions"), - errhint("Do not mix Gregorian and ISO week date " - "conventions in a formatting template."))); + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid combination of date conventions"), + errhint("Do not mix Gregorian and ISO week date " + "conventions in a formatting template.")); } + + return true; } /* @@ -2225,18 +2242,25 @@ from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode) * * Puke if the destination integer has previously been set to some other * non-zero value. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and false + * is returned. */ -static void -from_char_set_int(int *dest, const int value, const FormatNode *node) +static bool +from_char_set_int(int *dest, const int value, const FormatNode *node, + bool *error) { if (*dest != 0 && *dest != value) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("conflicting values for \"%s\" field in formatting string", - node->key->name), - errdetail("This value contradicts a previous setting for " - "the same field type."))); + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("conflicting values for \"%s\" field in " + "formatting string", + node->key->name), + errdetail("This value contradicts a previous setting " + "for the same field type.")); *dest = value; + + return true; } /* @@ -2258,9 +2282,14 @@ from_char_set_int(int *dest, const int value, const FormatNode *node) * Note that from_char_parse_int() provides a more convenient wrapper where * the length of the field is the same as the length of the format keyword (as * with DD and MI). + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and -1 + * is returned. + * */ static int -from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) +from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node, + bool *error) { long result; char copy[DCH_MAX_ITEM_SIZ + 1]; @@ -2293,50 +2322,56 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) char *last; if (used < len) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("source string too short for \"%s\" formatting field", - node->key->name), - errdetail("Field requires %d characters, but only %d " - "remain.", - len, used), - errhint("If your source string is not fixed-width, try " - "using the \"FM\" modifier."))); + dch_ereport(-1, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("source string too short for \"%s\" " + "formatting field", + node->key->name), + errdetail("Field requires %d characters, " + "but only %d remain.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier.")); errno = 0; result = strtol(copy, &last, 10); used = last - copy; if (used > 0 && used < len) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("Field requires %d characters, but only %d " - "could be parsed.", len, used), - errhint("If your source string is not fixed-width, try " - "using the \"FM\" modifier."))); + dch_ereport(-1, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Field requires %d characters, " + "but only %d could be parsed.", + len, used), + errhint("If your source string is not fixed-width, " + "try using the \"FM\" modifier.")); *src += used; } if (*src == init) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("Value must be an integer."))); + dch_ereport(-1, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("Value must be an integer.")); if (errno == ERANGE || result < INT_MIN || result > INT_MAX) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("value for \"%s\" in source string is out of range", - node->key->name), - errdetail("Value must be in the range %d to %d.", - INT_MIN, INT_MAX))); + dch_ereport(-1, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("value for \"%s\" in source string is out of range", + node->key->name), + errdetail("Value must be in the range %d to %d.", + INT_MIN, INT_MAX)); if (dest != NULL) - from_char_set_int(dest, (int) result, node); + { + if (!from_char_set_int(dest, (int) result, node, error)) + return -1; /* error */ + } + return *src - init; } @@ -2350,9 +2385,9 @@ from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) * required length explicitly. */ static int -from_char_parse_int(int *dest, char **src, FormatNode *node) +from_char_parse_int(int *dest, char **src, FormatNode *node, bool *error) { - return from_char_parse_int_len(dest, src, node->key->len, node); + return from_char_parse_int_len(dest, src, node->key->len, node, error); } /* ---------- @@ -2435,11 +2470,12 @@ seq_search(char *name, const char *const *array, int type, int max, int *len) * pointed to by 'dest', advance 'src' to the end of the part of the string * which matched, and return the number of characters consumed. * - * If the string doesn't match, throw an error. + * If the string doesn't match, throw an error if 'error' is NULL, otherwise + * set '*error' and return -1. */ static int -from_char_seq_search(int *dest, char **src, const char *const *array, int type, int max, - FormatNode *node) +from_char_seq_search(int *dest, char **src, const char *const *array, int type, + int max, FormatNode *node, bool *error) { int len; @@ -2451,12 +2487,12 @@ from_char_seq_search(int *dest, char **src, const char *const *array, int type, Assert(max <= DCH_MAX_ITEM_SIZ); strlcpy(copy, *src, max + 1); - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid value \"%s\" for \"%s\"", - copy, node->key->name), - errdetail("The given value did not match any of the allowed " - "values for this field."))); + dch_ereport(-1, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid value \"%s\" for \"%s\"", + copy, node->key->name), + errdetail("The given value did not match any of " + "the allowed values for this field.")); } *src += len; return len; @@ -3061,10 +3097,14 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col * * Note: we currently don't have any to_interval() function, so there * is no need here for INVALID_FOR_INTERVAL checks. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and false + * is returned. * ---------- */ -static void -DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) +static bool +DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict, + bool *error) { FormatNode *n; char *s; @@ -3147,7 +3187,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) continue; } - from_char_set_mode(out, n->key->date_mode); + if (!from_char_set_mode(out, n->key->date_mode, error)) + return false; /* error */ switch (n->key->id) { @@ -3158,40 +3199,49 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) case DCH_P_M: case DCH_a_m: case DCH_p_m: - from_char_seq_search(&value, &s, ampm_strings_long, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->pm, value % 2, n); + if (from_char_seq_search(&value, &s, ampm_strings_long, + ALL_UPPER, n->key->len, n, + error) < 0 || + !from_char_set_int(&out->pm, value % 2, n, error)) + return false; /* error */ out->clock = CLOCK_12_HOUR; break; case DCH_AM: case DCH_PM: case DCH_am: case DCH_pm: - from_char_seq_search(&value, &s, ampm_strings, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->pm, value % 2, n); + if (from_char_seq_search(&value, &s, ampm_strings, ALL_UPPER, + n->key->len, n, error) < 0 || + !from_char_set_int(&out->pm, value % 2, n, error)) + return false; out->clock = CLOCK_12_HOUR; break; case DCH_HH: case DCH_HH12: - from_char_parse_int_len(&out->hh, &s, 2, n); + if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0) + return false; out->clock = CLOCK_12_HOUR; SKIP_THth(s, n->suffix); break; case DCH_HH24: - from_char_parse_int_len(&out->hh, &s, 2, n); + if (from_char_parse_int_len(&out->hh, &s, 2, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_MI: - from_char_parse_int(&out->mi, &s, n); + if (from_char_parse_int(&out->mi, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_SS: - from_char_parse_int(&out->ss, &s, n); + if (from_char_parse_int(&out->ss, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_MS: /* millisecond */ - len = from_char_parse_int_len(&out->ms, &s, 3, n); + len = from_char_parse_int_len(&out->ms, &s, 3, n, error); + if (len < 0) + return false; /* * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25 @@ -3212,7 +3262,9 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) case DCH_US: /* microsecond */ len = from_char_parse_int_len(&out->us, &s, n->key->id == DCH_US ? 6 : - out->ff, n); + out->ff, n, error); + if (len < 0) + return false; out->us *= len == 1 ? 100000 : len == 2 ? 10000 : @@ -3223,16 +3275,17 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) SKIP_THth(s, n->suffix); break; case DCH_SSSS: - from_char_parse_int(&out->ssss, &s, n); + if (from_char_parse_int(&out->ssss, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_tz: case DCH_TZ: case DCH_OF: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("formatting field \"%s\" is only supported in to_char", - n->key->name))); + dch_ereport(false, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only supported in to_char", + n->key->name)); break; case DCH_TZH: @@ -3256,82 +3309,97 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) out->tzsign = +1; } - from_char_parse_int_len(&out->tzh, &s, 2, n); + if (from_char_parse_int_len(&out->tzh, &s, 2, n, error) < 0) + return false; break; case DCH_TZM: /* assign positive timezone sign if TZH was not seen before */ if (!out->tzsign) out->tzsign = +1; - from_char_parse_int_len(&out->tzm, &s, 2, n); + if (from_char_parse_int_len(&out->tzm, &s, 2, n, error) < 0) + return false; break; case DCH_A_D: case DCH_B_C: case DCH_a_d: case DCH_b_c: - from_char_seq_search(&value, &s, adbc_strings_long, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->bc, value % 2, n); + if (from_char_seq_search(&value, &s, adbc_strings_long, + ALL_UPPER, n->key->len, n, + error) < 0 || + !from_char_set_int(&out->bc, value % 2, n, error)) + return false; break; case DCH_AD: case DCH_BC: case DCH_ad: case DCH_bc: - from_char_seq_search(&value, &s, adbc_strings, - ALL_UPPER, n->key->len, n); - from_char_set_int(&out->bc, value % 2, n); + if (from_char_seq_search(&value, &s, adbc_strings, ALL_UPPER, + n->key->len, n, error) < 0 || + !from_char_set_int(&out->bc, value % 2, n, error)) + return false; break; case DCH_MONTH: case DCH_Month: case DCH_month: - from_char_seq_search(&value, &s, months_full, ONE_UPPER, - MAX_MONTH_LEN, n); - from_char_set_int(&out->mm, value + 1, n); + if (from_char_seq_search(&value, &s, months_full, ONE_UPPER, + MAX_MONTH_LEN, n, error) < 0 || + !from_char_set_int(&out->mm, value + 1, n, error)) + return false; break; case DCH_MON: case DCH_Mon: case DCH_mon: - from_char_seq_search(&value, &s, months, ONE_UPPER, - MAX_MON_LEN, n); - from_char_set_int(&out->mm, value + 1, n); + if (from_char_seq_search(&value, &s, months, ONE_UPPER, + MAX_MON_LEN, n, error) < 0 || + !from_char_set_int(&out->mm, value + 1, n, error)) + return false; break; case DCH_MM: - from_char_parse_int(&out->mm, &s, n); + if (from_char_parse_int(&out->mm, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_DAY: case DCH_Day: case DCH_day: - from_char_seq_search(&value, &s, days, ONE_UPPER, - MAX_DAY_LEN, n); - from_char_set_int(&out->d, value, n); + if (from_char_seq_search(&value, &s, days, ONE_UPPER, + MAX_DAY_LEN, n, error) < 0 || + !from_char_set_int(&out->d, value, n, error)) + return false; out->d++; break; case DCH_DY: case DCH_Dy: case DCH_dy: - from_char_seq_search(&value, &s, days, ONE_UPPER, - MAX_DY_LEN, n); - from_char_set_int(&out->d, value, n); + if (from_char_seq_search(&value, &s, days, ONE_UPPER, + MAX_DY_LEN, n, error) < 0 || + !from_char_set_int(&out->d, value, n, error)) + return false; out->d++; break; case DCH_DDD: - from_char_parse_int(&out->ddd, &s, n); + if (from_char_parse_int(&out->ddd, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_IDDD: - from_char_parse_int_len(&out->ddd, &s, 3, n); + if (from_char_parse_int_len(&out->ddd, &s, 3, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_DD: - from_char_parse_int(&out->dd, &s, n); + if (from_char_parse_int(&out->dd, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_D: - from_char_parse_int(&out->d, &s, n); + if (from_char_parse_int(&out->d, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_ID: - from_char_parse_int_len(&out->d, &s, 1, n); + if (from_char_parse_int_len(&out->d, &s, 1, n, error) < 0) + return false; /* Shift numbering to match Gregorian where Sunday = 1 */ if (++out->d > 7) out->d = 1; @@ -3339,7 +3407,8 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) break; case DCH_WW: case DCH_IW: - from_char_parse_int(&out->ww, &s, n); + if (from_char_parse_int(&out->ww, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_Q: @@ -3354,11 +3423,13 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) * We still parse the source string for an integer, but it * isn't stored anywhere in 'out'. */ - from_char_parse_int((int *) NULL, &s, n); + if (from_char_parse_int((int *) NULL, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_CC: - from_char_parse_int(&out->cc, &s, n); + if (from_char_parse_int(&out->cc, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_Y_YYY: @@ -3370,11 +3441,12 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch); if (matched < 2) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("invalid input string for \"Y,YYY\""))); + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("invalid input string for \"Y,YYY\"")); years += (millennia * 1000); - from_char_set_int(&out->year, years, n); + if (!from_char_set_int(&out->year, years, n, error)) + return false; out->yysz = 4; s += nch; SKIP_THth(s, n->suffix); @@ -3382,47 +3454,63 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) break; case DCH_YYYY: case DCH_IYYY: - from_char_parse_int(&out->year, &s, n); + if (from_char_parse_int(&out->year, &s, n, error) < 0) + return false; out->yysz = 4; SKIP_THth(s, n->suffix); break; case DCH_YYY: case DCH_IYY: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, error); + if (len < 0) + return false; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 3; SKIP_THth(s, n->suffix); break; case DCH_YY: case DCH_IY: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, error); + if (len < 0) + return false; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 2; SKIP_THth(s, n->suffix); break; case DCH_Y: case DCH_I: - if (from_char_parse_int(&out->year, &s, n) < 4) + len = from_char_parse_int(&out->year, &s, n, error); + if (len < 0) + return false; + if (len < 4) out->year = adjust_partial_year_to_2020(out->year); out->yysz = 1; SKIP_THth(s, n->suffix); break; case DCH_RM: - from_char_seq_search(&value, &s, rm_months_upper, - ALL_UPPER, MAX_RM_LEN, n); - from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); + if (from_char_seq_search(&value, &s, rm_months_upper, + ALL_UPPER, MAX_RM_LEN, n, error) < 0 || + !from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n, + error)) + return false; break; case DCH_rm: - from_char_seq_search(&value, &s, rm_months_lower, - ALL_LOWER, MAX_RM_LEN, n); - from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n); + if (from_char_seq_search(&value, &s, rm_months_lower, + ALL_LOWER, MAX_RM_LEN, n, error) < 0 || + !from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n, + error)) + return false; break; case DCH_W: - from_char_parse_int(&out->w, &s, n); + if (from_char_parse_int(&out->w, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; case DCH_J: - from_char_parse_int(&out->j, &s, n); + if (from_char_parse_int(&out->j, &s, n, error) < 0) + return false; SKIP_THth(s, n->suffix); break; } @@ -3442,19 +3530,21 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) if (strict) { if (n->type != NODE_TYPE_END) - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("input string is too short for datetime format"))); + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("input string is too short for datetime format")); while (*s != '\0' && isspace((unsigned char) *s)) s++; if (*s != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("trailing characters remain in input string after " - "datetime format"))); + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string " + "after datetime format")); } + + return true; } /* @@ -3475,9 +3565,14 @@ DCH_prevent_counter_overflow(void) } } -/* Get mask of date/time/zone components present in format nodes. */ +/* + * Get mask of date/time/zone components present in format nodes. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and 0 + * is returned. + */ static int -DCH_datetime_type(FormatNode *node) +DCH_datetime_type(FormatNode *node, bool *error) { FormatNode *n; int flags = 0; @@ -3518,10 +3613,11 @@ DCH_datetime_type(FormatNode *node) case DCH_tz: case DCH_TZ: case DCH_OF: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("formatting field \"%s\" is only supported in to_char", - n->key->name))); + dch_ereport(0, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("formatting field \"%s\" is only " + "supported in to_char", + n->key->name)); flags |= DCH_ZONED; break; case DCH_TZH: @@ -3850,6 +3946,72 @@ interval_to_char(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(res); } +/* + * Decode the specified or default timezone, if any, or use the session + * timezone if it is allowed. + */ +static bool +get_timezone(struct pg_tm *tm, char *default_tz_name, text *date_txt, + const char *type_name, bool allow_session_timezone, + int *tz, bool *error) +{ + /* Use the specified or default time zone, if any. */ + char *tz_name = + tm->tm_zone ? unconstify(char *, tm->tm_zone) : default_tz_name; + + if (tz_name) + { + int dterr = DecodeTimezone(tz_name, tz); + + if (dterr) + { + if (error) + { + *error = true; + return false; + } + + DateTimeParseError(dterr, + date_txt ? text_to_cstring(date_txt) : tz_name, + type_name); + } + } + else if (*tz == PG_INT32_MIN) + { + if (!allow_session_timezone) + dch_ereport(false, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time-zone in input string for type %s", + type_name)); + + *tz = DetermineTimeZoneOffset(tm, session_timezone); + } + + return true; +} + +/* + * Convert pg_tm to timestamp ('tz' is NULL) or timestamptz ('tz' is not NULL) + * using the specified fractional precision 'typmod'. + */ +static inline Timestamp +tm_to_timestamp(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz, + bool *error) +{ + Timestamp result; + + if (tm2timestamp(tm, fsec, tz, &result) != 0) + dch_ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + /* Use the specified fractional precision, if any. */ + if (typmod != -1) + AdjustTimestampForTypmodError(&result, typmod, error); + + return result; +} + /* --------------------- * TO_TIMESTAMP() * @@ -3862,35 +4024,45 @@ to_timestamp(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_PP(0); text *fmt = PG_GETARG_TEXT_PP(1); - Timestamp result; - int tz; + int tz = PG_INT32_MIN; struct pg_tm tm; fsec_t fsec; int fprec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL, NULL); /* Use the specified time zone, if any. */ - if (tm.tm_zone) - { - int dterr = DecodeTimezone(unconstify(char *, tm.tm_zone), &tz); + get_timezone(&tm, NULL, date_txt, "timestamp", true, &tz, NULL); - if (dterr) - DateTimeParseError(dterr, text_to_cstring(date_txt), "timestamptz"); - } - else - tz = DetermineTimeZoneOffset(&tm, session_timezone); + PG_RETURN_DATUM(tm_to_timestamp(&tm, fsec, fprec ? fprec : -1, &tz, NULL)); +} - if (tm2timestamp(&tm, fsec, &tz, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); +/* + * Convert pg_tm to date with out of range checking. + */ +static DateADT +tm_to_date(struct pg_tm *tm, text *date_txt, bool *error) +{ + DateADT result; - /* Use the specified fractional precision, if any. */ - if (fprec) - AdjustTimestampForTypmod(&result, fprec); + /* Prevent overflow in Julian-day routines */ + if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) + dch_ereport((Datum) 0, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt))); + + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - + POSTGRES_EPOCH_JDATE; + + /* Now check for just-out-of-range dates */ + if (!IS_VALID_DATE(result)) + dch_ereport((Datum) 0, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range: \"%s\"", + text_to_cstring(date_txt))); - PG_RETURN_TIMESTAMP(result); + return DateADTGetDatum(result); } /* ---------- @@ -3903,191 +4075,135 @@ to_date(PG_FUNCTION_ARGS) { text *date_txt = PG_GETARG_TEXT_PP(0); text *fmt = PG_GETARG_TEXT_PP(1); - DateADT result; struct pg_tm tm; fsec_t fsec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL); + do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL, NULL); - /* Prevent overflow in Julian-day routines */ - if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); + PG_RETURN_DATUM(tm_to_date(&tm, date_txt, NULL)); +} - result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; +/* + * Convert pg_tm to timetz using the specified fractional precision 'typmod' + * and timezone 'tz'. + */ +static Datum +tm_to_timetz(struct pg_tm *tm, fsec_t fsec, int32 typmod, int *tz, bool *error) +{ + TimeTzADT *result = palloc(sizeof(TimeTzADT)); - /* Now check for just-out-of-range dates */ - if (!IS_VALID_DATE(result)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); + if (tm2timetz(tm, fsec, *tz, result) != 0) + dch_ereport((Datum) 0, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timetz out of range")); + + if (typmod != -1) + AdjustTimeForTypmod(&result->time, typmod); + + return TimeTzADTPGetDatum(result); +} + +/* + * Convert pg_tm to time using the specified fractional precision 'typmod'. + */ +static Datum +tm_to_time(struct pg_tm *tm, fsec_t fsec, int32 typmod, bool *error) +{ + TimeADT result; + + if (tm2time(tm, fsec, &result) != 0) + dch_ereport((Datum) 0, + errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("time out of range")); + + if (typmod != -1) + AdjustTimeForTypmod(&result, typmod); - PG_RETURN_DATEADT(result); + return TimeADTGetDatum(result); } /* * Make datetime type from 'date_txt' which is formated at argument 'fmt'. * Actual datatype (returned in 'typid', 'typmod') is determined by * presence of date/time/zone components in the format string. + * + * Default time-zone for tz types is specified with 'tzname'. If 'tzname' is + * NULL and the input string does not contain zone components then "missing tz" + * error is thrown. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and + * ((Datum) 0) is returned. */ Datum -to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid, - int32 *typmod, int *tz) +parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, + Oid *typid, int32 *typmod, int *tz, bool *error) { struct pg_tm tm; fsec_t fsec; int fprec = 0; int flags; - do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags); + if (!do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags, + error)) + return (Datum) 0; - *typmod = fprec ? fprec : -1; /* fractional part precision */ - *tz = 0; if (flags & DCH_DATED) { if (flags & DCH_TIMED) { + *typmod = fprec ? fprec : -1; /* fractional part precision */ + if (flags & DCH_ZONED) { - TimestampTz result; - - if (tm.tm_zone) - tzname = (char *) tm.tm_zone; - - if (tzname) - { - int dterr = DecodeTimezone(tzname, tz); - - if (dterr) - DateTimeParseError(dterr, tzname, "timestamptz"); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time-zone in timestamptz input string"))); - - *tz = DetermineTimeZoneOffset(&tm, session_timezone); - } - - if (tm2timestamp(&tm, fsec, tz, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamptz out of range"))); - - AdjustTimestampForTypmod(&result, *typmod); + if (!get_timezone(&tm, tzname, NULL, "timestamptz", false, tz, + error)) + return (Datum) 0; *typid = TIMESTAMPTZOID; - return TimestampTzGetDatum(result); + return tm_to_timestamp(&tm, fsec, *typmod, tz, error); } else { - Timestamp result; - - if (tm2timestamp(&tm, fsec, NULL, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - AdjustTimestampForTypmod(&result, *typmod); - *typid = TIMESTAMPOID; - return TimestampGetDatum(result); + return tm_to_timestamp(&tm, fsec, *typmod, NULL, error); } } else { if (flags & DCH_ZONED) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is zoned but not timed"))); - } - else - { - DateADT result; - - /* Prevent overflow in Julian-day routines */ - if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); - - result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - - POSTGRES_EPOCH_JDATE; + dch_ereport((Datum) 0, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is zoned but not timed")); - /* Now check for just-out-of-range dates */ - if (!IS_VALID_DATE(result)) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range: \"%s\"", - text_to_cstring(date_txt)))); - - *typid = DATEOID; - return DateADTGetDatum(result); - } + *typid = DATEOID; + *typmod = -1; + return tm_to_date(&tm, date_txt, error); } } else if (flags & DCH_TIMED) { + *typmod = fprec ? fprec : -1; /* fractional part precision */ + if (flags & DCH_ZONED) { - TimeTzADT *result = palloc(sizeof(TimeTzADT)); - - if (tm.tm_zone) - tzname = (char *) tm.tm_zone; - - if (tzname) - { - int dterr = DecodeTimezone(tzname, tz); - - if (dterr) - DateTimeParseError(dterr, tzname, "timetz"); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("missing time-zone in timestamptz input string"))); - - *tz = DetermineTimeZoneOffset(&tm, session_timezone); - } - - if (tm2timetz(&tm, fsec, *tz, result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timetz out of range"))); - - AdjustTimeForTypmod(&result->time, *typmod); + if (!get_timezone(&tm, tzname, NULL, "timetz", false, tz, error)) + return (Datum) 0; *typid = TIMETZOID; - return TimeTzADTPGetDatum(result); + return tm_to_timetz(&tm, fsec, *typmod, tz, error); } else { - TimeADT result; - - if (tm2time(&tm, fsec, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("time out of range"))); - - AdjustTimeForTypmod(&result, *typmod); - *typid = TIMEOID; - return TimeADTGetDatum(result); + return tm_to_time(&tm, fsec, *typmod, error); } } else { - ereport(ERROR, - (errcode(ERRCODE_INVALID_DATETIME_FORMAT), - errmsg("datetime format is not dated and not timed"))); + dch_ereport((Datum) 0, + errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("datetime format is not dated and not timed")); } return (Datum) 0; @@ -4110,10 +4226,13 @@ to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid, * * 'strict' enables error reporting on unmatched trailing characters in input or * format strings patterns. + * + * If 'error' is NULL, then errors are thrown, else '*error' is set and false + * is returned. */ -static void -do_to_timestamp(text *date_txt, text *fmt, bool strict, - struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags) +static bool +do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm, + fsec_t *fsec, int *fprec, int *flags, bool *error) { FormatNode *format; TmFromChar tmfc; @@ -4166,15 +4285,18 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, /* dump_index(DCH_keywords, DCH_index); */ #endif - DCH_from_char(format, date_str, &tmfc, strict); + DCH_from_char(format, date_str, &tmfc, strict, error); pfree(fmt_str); - if (flags) - *flags = DCH_datetime_type(format); + if (flags && (!error || !*error)) + *flags = DCH_datetime_type(format, error); if (!incache) pfree(format); + + if (error && *error) + goto err; } DEBUG_TMFC(&tmfc); @@ -4203,11 +4325,15 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (tmfc.clock == CLOCK_12_HOUR) { if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2) + { + if (error) + goto err; ereport(ERROR, (errcode(ERRCODE_INVALID_DATETIME_FORMAT), errmsg("hour \"%d\" is invalid for the 12-hour clock", tm->tm_hour), errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); + } if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2) tm->tm_hour += HOURS_PER_DAY / 2; @@ -4311,9 +4437,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, */ if (!tm->tm_year && !tmfc.bc) + { + if (error) + goto err; ereport(ERROR, (errcode(ERRCODE_INVALID_DATETIME_FORMAT), errmsg("cannot calculate day of year without year information"))); + } if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) { @@ -4365,6 +4495,9 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (dterr != 0) { + if (error) + goto err; + /* * Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an @@ -4379,7 +4512,12 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR || tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE || *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) + { + if (error) + goto err; + DateTimeParseError(DTERR_FIELD_OVERFLOW, date_str, "timestamp"); + } /* Save parsed time-zone into tm->tm_zone if it was specified */ if (tmfc.tzsign) @@ -4388,7 +4526,12 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR || tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR) + { + if (error) + goto err; + DateTimeParseError(DTERR_TZDISP_OVERFLOW, date_str, "timestamp"); + } tz = psprintf("%c%02d:%02d", tmfc.tzsign > 0 ? '+' : '-', tmfc.tzh, tmfc.tzm); @@ -4399,6 +4542,12 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, DEBUG_TM(tm); pfree(date_str); + return true; + +err: + *error = true; + pfree(date_str); + return false; } diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 26d293709a..7440f77cbe 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, { char buf[MAXDATELEN + 1]; - JsonEncodeDateTime(buf, val, DATEOID); + JsonEncodeDateTime(buf, val, DATEOID, NULL); appendStringInfo(result, "\"%s\"", buf); } break; @@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, { char buf[MAXDATELEN + 1]; - JsonEncodeDateTime(buf, val, TIMESTAMPOID); + JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL); appendStringInfo(result, "\"%s\"", buf); } break; @@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, { char buf[MAXDATELEN + 1]; - JsonEncodeDateTime(buf, val, TIMESTAMPTZOID); + JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL); appendStringInfo(result, "\"%s\"", buf); } break; @@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result, /* * Encode 'value' of datetime type 'typid' into JSON string in ISO format using - * optionally preallocated buffer 'buf'. + * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone + * offset (in seconds) in which we want to show timestamptz. */ char * -JsonEncodeDateTime(char *buf, Datum value, Oid typid) +JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp) { if (!buf) buf = palloc(MAXDATELEN + 1); @@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid) const char *tzn = NULL; timestamp = DatumGetTimestampTz(value); + + /* + * If time-zone is specified, we apply a time-zone shift, + * convert timestamptz to pg_tm as if it was without + * time-zone, and then use specified time-zone for encoding + * timestamp into a string. + */ + if (tzp) + { + tz = *tzp; + timestamp -= (TimestampTz) tz * USECS_PER_SEC; + } + /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) EncodeSpecialTimestamp(timestamp, buf); - else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) + else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec, + tzp ? NULL : &tzn, NULL) == 0) + { + if (tzp) + tm.tm_isdst = 1; /* set time-zone presence flag */ + EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); + } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 69f41ab455..74b4bbe44c 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -206,6 +206,24 @@ JsonbTypeName(JsonbValue *jbv) return "boolean"; case jbvNull: return "null"; + case jbvDatetime: + switch (jbv->val.datetime.typid) + { + case DATEOID: + return "date"; + case TIMEOID: + return "time without time zone"; + case TIMETZOID: + return "time with time zone"; + case TIMESTAMPOID: + return "timestamp without time zone"; + case TIMESTAMPTZOID: + return "timestamp with time zone"; + default: + elog(ERROR, "unrecognized jsonb value datetime type: %d", + jbv->val.datetime.typid); + } + return "unknown"; default: elog(ERROR, "unrecognized jsonb value type: %d", jbv->type); return "unknown"; @@ -805,17 +823,20 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, break; case JSONBTYPE_DATE: jb.type = jbvString; - jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID); + jb.val.string.val = JsonEncodeDateTime(NULL, val, + DATEOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_TIMESTAMP: jb.type = jbvString; - jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID); + jb.val.string.val = JsonEncodeDateTime(NULL, val, + TIMESTAMPOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_TIMESTAMPTZ: jb.type = jbvString; - jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID); + jb.val.string.val = JsonEncodeDateTime(NULL, val, + TIMESTAMPTZOID, NULL); jb.val.string.len = strlen(jb.val.string.val); break; case JSONBTYPE_JSONCAST: diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 7969f6f584..cb2bd872cf 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -14,9 +14,12 @@ #include "postgres.h" #include "catalog/pg_collation.h" +#include "catalog/pg_type.h" #include "miscadmin.h" #include "utils/builtins.h" +#include "utils/datetime.h" #include "utils/hashutils.h" +#include "utils/jsonapi.h" #include "utils/jsonb.h" #include "utils/memutils.h" #include "utils/varlena.h" @@ -241,6 +244,7 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1; break; case jbvBinary: + case jbvDatetime: elog(ERROR, "unexpected jbvBinary value"); } } @@ -1749,6 +1753,22 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE; break; + case jbvDatetime: + { + char buf[MAXDATELEN + 1]; + size_t len; + + JsonEncodeDateTime(buf, + scalarVal->val.datetime.value, + scalarVal->val.datetime.typid, + &scalarVal->val.datetime.tz); + len = strlen(buf); + appendToBuffer(buffer, buf, len); + + *jentry = JENTRY_ISSTRING | len; + } + break; + default: elog(ERROR, "invalid jsonb scalar type"); } diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 87ae60e490..8fba0e97d0 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -286,6 +286,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiDiv: case jpiMod: case jpiStartsWith: + case jpiDatetime: { /* * First, reserve place for left/right arg's positions, then @@ -692,6 +693,22 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, case jpiDouble: appendBinaryStringInfo(buf, ".double()", 9); break; + case jpiDatetime: + appendBinaryStringInfo(buf, ".datetime(", 10); + if (v->content.args.left) + { + jspGetLeftArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + + if (v->content.args.right) + { + appendBinaryStringInfo(buf, ", ", 2); + jspGetRightArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + } + appendStringInfoChar(buf, ')'); + break; case jpiKeyValue: appendBinaryStringInfo(buf, ".keyvalue()", 11); break; @@ -754,6 +771,8 @@ jspOperationName(JsonPathItemType type) return "floor"; case jpiCeiling: return "ceiling"; + case jpiDatetime: + return "datetime"; default: elog(ERROR, "unrecognized jsonpath item type: %d", type); return NULL; @@ -874,6 +893,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiLessOrEqual: case jpiGreaterOrEqual: case jpiStartsWith: + case jpiDatetime: read_int32(v->content.args.left, base, pos); read_int32(v->content.args.right, base, pos); break; @@ -961,6 +981,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiFloor || v->type == jpiCeiling || v->type == jpiDouble || + v->type == jpiDatetime || v->type == jpiKeyValue || v->type == jpiStartsWith); @@ -988,6 +1009,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || + v->type == jpiDatetime || v->type == jpiStartsWith); jspInitByBuffer(a, v->base, v->content.args.left); @@ -1009,6 +1031,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || + v->type == jpiDatetime || v->type == jpiStartsWith); jspInitByBuffer(a, v->base, v->content.args.right); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 2a5f9a2386..732e261bbf 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -66,6 +66,7 @@ #include "miscadmin.h" #include "regex/regex.h" #include "utils/builtins.h" +#include "utils/datetime.h" #include "utils/datum.h" #include "utils/formatting.h" #include "utils/float.h" @@ -248,6 +249,13 @@ static int JsonbType(JsonbValue *jb); static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items); +static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, + bool strict, Datum *value, Oid *typid, + int32 *typmod, int *tzp, bool throwErrors); +static int compareDatetime(Datum val1, Oid typid1, int tz1, + Datum val2, Oid typid2, int tz2, + bool *error); + /****************** User interface to JsonPath executor ********************/ /* @@ -1029,6 +1037,162 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } break; + case jpiDatetime: + { + JsonbValue jbvbuf; + Datum value; + text *datetime; + Oid typid; + int32 typmod = -1; + int tz = PG_INT32_MIN; + bool hasNext; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (!(jb = getScalar(jb, jbvString))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), + errmsg("jsonpath item method .%s() can only be applied to a string value", + jspOperationName(jsp->type))))); + + datetime = cstring_to_text_with_len(jb->val.string.val, + jb->val.string.len); + + if (jsp->content.args.left) + { + text *template; + char *template_str; + int template_len; + char *tzname = NULL; + + jspGetLeftArg(jsp, &elem); + + if (elem.type != jpiString) + elog(ERROR, "invalid jsonpath item type for .datetime() argument"); + + template_str = jspGetString(&elem, &template_len); + + if (jsp->content.args.right) + { + JsonValueList tzlist = {0}; + JsonPathExecResult tzres; + JsonbValue *tzjbv; + + jspGetRightArg(jsp, &elem); + tzres = executeItem(cxt, &elem, jb, &tzlist); + if (jperIsError(tzres)) + return tzres; + + if (JsonValueListLength(&tzlist) != 1 || + ((tzjbv = JsonValueListHead(&tzlist))->type != jbvString && + tzjbv->type != jbvNumeric)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), + errmsg("timezone argument of jsonpath item method .%s() is not a singleton string or number", + jspOperationName(jsp->type))))); + + if (tzjbv->type == jbvString) + tzname = pnstrdup(tzjbv->val.string.val, + tzjbv->val.string.len); + else + { + bool error = false; + + tz = numeric_int4_opt_error(tzjbv->val.numeric, + &error); + + if (error || tz == PG_INT32_MIN) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), + errmsg("timezone argument of jsonpath item method .%s() is out of integer range", + jspOperationName(jsp->type))))); + + tz = -tz; + } + } + + template = cstring_to_text_with_len(template_str, + template_len); + + if (tryToParseDatetime(template, datetime, tzname, false, + &value, &typid, &typmod, + &tz, jspThrowErrors(cxt))) + res = jperOk; + else + res = jperError; + + if (tzname) + pfree(tzname); + } + else + { + /* Try to recognize one of ISO formats. */ + static const char *fmt_str[] = + { + "yyyy-mm-dd HH24:MI:SS TZH:TZM", + "yyyy-mm-dd HH24:MI:SS TZH", + "yyyy-mm-dd HH24:MI:SS", + "yyyy-mm-dd", + "HH24:MI:SS TZH:TZM", + "HH24:MI:SS TZH", + "HH24:MI:SS" + }; + + /* cache for format texts */ + static text *fmt_txt[lengthof(fmt_str)] = {0}; + int i; + + for (i = 0; i < lengthof(fmt_str); i++) + { + if (!fmt_txt[i]) + { + MemoryContext oldcxt = + MemoryContextSwitchTo(TopMemoryContext); + + fmt_txt[i] = cstring_to_text(fmt_str[i]); + MemoryContextSwitchTo(oldcxt); + } + + if (tryToParseDatetime(fmt_txt[i], datetime, NULL, + true, &value, &typid, &typmod, + &tz, false)) + { + res = jperOk; + break; + } + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), + errmsg("unrecognized datetime format"), + errhint("use datetime template argument for explicit format specification")))); + } + + pfree(datetime); + + if (jperIsError(res)) + break; + + hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found) + break; + + jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); + + jb->type = jbvDatetime; + jb->val.datetime.value = value; + jb->val.datetime.typid = typid; + jb->val.datetime.typmod = typmod; + jb->val.datetime.tz = tz; + + res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext); + } + break; + case jpiKeyValue: if (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); @@ -2022,6 +2186,22 @@ compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2) jb2->val.string.val, jb2->val.string.len, DEFAULT_COLLATION_OID); break; + case jbvDatetime: + { + bool error = false; + + cmp = compareDatetime(jb1->val.datetime.value, + jb1->val.datetime.typid, + jb1->val.datetime.tz, + jb2->val.datetime.value, + jb2->val.datetime.typid, + jb2->val.datetime.tz, + &error); + + if (error) + return jpbUnknown; + } + break; case jbvBinary: case jbvArray: @@ -2278,3 +2458,293 @@ wrapItemsInArray(const JsonValueList *items) return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } + +#define USE_CURRENT_TZ + +static int +get_current_tz() +{ + struct pg_tm tm; + + GetCurrentDateTime(&tm); + + return DetermineTimeZoneOffset(&tm, session_timezone); +} + +static inline Datum +time_to_timetz(Datum time, int tz, bool *error) +{ + TimeADT tm = DatumGetTimeADT(time); + TimeTzADT *result = palloc(sizeof(TimeTzADT)); + + if (tz == PG_INT32_MIN) + { +#ifdef USE_CURRENT_TZ + tz = get_current_tz(); +#else + *error = true; + return (Datum) 0; +#endif + } + + result->time = tm; + result->zone = tz; + + return TimeTzADTPGetDatum(result); +} + +static inline Datum +date_to_timestamp(Datum date, bool *error) +{ + DateADT dt = DatumGetDateADT(date); + Timestamp ts = date2timestamp_internal(dt, error); + + return TimestampGetDatum(ts); +} + +static inline Datum +date_to_timestamptz(Datum date, int tz, bool *error) +{ + DateADT dt = DatumGetDateADT(date); + TimestampTz ts; + + if (tz == PG_INT32_MIN) + { +#ifdef USE_CURRENT_TZ + tz = get_current_tz(); +#else + *error = true; + return (Datum) 0; +#endif + } + + ts = date2timestamptz_internal(dt, &tz, error); + + return TimestampTzGetDatum(ts); +} + +static inline Datum +timestamp_to_timestamptz(Datum val, int tz, bool *error) +{ + Timestamp ts = DatumGetTimestamp(val); + TimestampTz tstz; + + if (tz == PG_INT32_MIN) + { +#ifdef USE_CURRENT_TZ + tz = get_current_tz(); +#else + *error = true; + return (Datum) 0; +#endif + } + + tstz = timestamp2timestamptz_internal(ts, &tz, error); + + return TimestampTzGetDatum(tstz); +} + +/* + * Cross-type comparison of two datetime SQL/JSON items. If items are + * uncomparable, 'error' flag is set. + */ +static int +compareDatetime(Datum val1, Oid typid1, int tz1, + Datum val2, Oid typid2, int tz2, + bool *error) +{ + PGFunction cmpfunc = NULL; + + switch (typid1) + { + case DATEOID: + switch (typid2) + { + case DATEOID: + cmpfunc = date_cmp; + + break; + + case TIMESTAMPOID: + val1 = date_to_timestamp(val1, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPTZOID: + val1 = date_to_timestamptz(val1, tz1, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + case TIMEOID: + switch (typid2) + { + case TIMEOID: + cmpfunc = time_cmp; + + break; + + case TIMETZOID: + val1 = time_to_timetz(val1, tz1, error); + cmpfunc = timetz_cmp; + + break; + + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + *error = true; + return 0; + } + break; + + case TIMETZOID: + switch (typid2) + { + case TIMEOID: + val2 = time_to_timetz(val2, tz2, error); + cmpfunc = timetz_cmp; + + break; + + case TIMETZOID: + cmpfunc = timetz_cmp; + + break; + + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + *error = true; + return 0; + } + break; + + case TIMESTAMPOID: + switch (typid2) + { + case DATEOID: + val2 = date_to_timestamp(val2, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPOID: + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPTZOID: + val1 = timestamp_to_timestamptz(val1, tz1, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + case TIMESTAMPTZOID: + switch (typid2) + { + case DATEOID: + val2 = date_to_timestamptz(val2, tz2, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPOID: + val2 = timestamp_to_timestamptz(val2, tz2, error); + cmpfunc = timestamp_cmp; + + break; + + case TIMESTAMPTZOID: + cmpfunc = timestamp_cmp; + + break; + + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d", + typid1); + } + + if (*error) + return 0; + + if (!cmpfunc) + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %d", + typid2); + + *error = false; + + return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); +} + +/* + * Try to parse datetime text with the specified datetime template and + * default time-zone 'tzname'. + * Returns 'value' datum, its type 'typid' and 'typmod'. + * Datetime error is rethrown with SQL/JSON errcode if 'throwErrors' is true. + */ +static bool +tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, + Datum *value, Oid *typid, int32 *typmod, int *tzp, + bool throwErrors) +{ + bool error = false; + int tz = *tzp; + +#if 0 + if (throwErrors) + { + PG_TRY(); + { + *value = parse_datetime(datetime, fmt, tzname, strict, typid, + typmod, &tz, throwErrors ? NULL : &error); + } + PG_CATCH(); + { + if (/* ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION */ + geterrcode() == ERRCODE_INVALID_DATETIME_FORMAT || + geterrcode() == ERRCODE_DATETIME_VALUE_OUT_OF_RANGE) + { + /* + * Save original datetime error message, details and hint, just + * replace errcode with a SQL/JSON one. + */ + errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + } + PG_RE_THROW(); + } + PG_END_TRY(); + } + else +#endif + + *value = parse_datetime(datetime, fmt, tzname, strict, typid, typmod, + &tz, throwErrors ? NULL : &error); + + if (!error) + *tzp = tz; + + return !error; +} diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index a0a930ccf0..ae06e18560 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -94,12 +94,14 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P %token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P +%token DATETIME_P %type result %type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial expr_or_predicate + datetime_template opt_datetime_template %type accessor_expr @@ -247,9 +249,22 @@ accessor_op: | array_accessor { $$ = $1; } | '.' any_path { $$ = $2; } | '.' method '(' ')' { $$ = makeItemType($2); } + | '.' DATETIME_P '(' opt_datetime_template ')' + { $$ = makeItemBinary(jpiDatetime, $4, NULL); } + | '.' DATETIME_P '(' datetime_template ',' expr ')' + { $$ = makeItemBinary(jpiDatetime, $4, $6); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } ; +datetime_template: + STRING_P { $$ = makeItemString(&$1); } + ; + +opt_datetime_template: + datetime_template { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + key: key_name { $$ = makeItemKey(&$1); } ; @@ -272,6 +287,7 @@ key_name: | FLOOR_P | DOUBLE_P | CEILING_P + | DATETIME_P | KEYVALUE_P | LAST_P | STARTS_P diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index feb282ea88..1c291fd914 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -308,6 +308,7 @@ static const JsonPathKeyword keywords[] = { { 6, false, STRICT_P, "strict"}, { 7, false, CEILING_P, "ceiling"}, { 7, false, UNKNOWN_P, "unknown"}, + { 8, false, DATETIME_P, "datetime"}, { 8, false, KEYVALUE_P, "keyvalue"}, { 10,false, LIKE_REGEX_P, "like_regex"}, }; diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 4d00e996b2..cd104246cd 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -337,11 +337,11 @@ timestamp_scale(PG_FUNCTION_ARGS) } /* - * AdjustTimestampForTypmod --- round off a timestamp to suit given typmod + * AdjustTimestampForTypmodError --- round off a timestamp to suit given typmod * Works for either timestamp or timestamptz. */ -void -AdjustTimestampForTypmod(Timestamp *time, int32 typmod) +bool +AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, bool *error) { static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = { INT64CONST(1000000), @@ -367,10 +367,18 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) && (typmod != -1) && (typmod != MAX_TIMESTAMP_PRECISION)) { if (typmod < 0 || typmod > MAX_TIMESTAMP_PRECISION) + { + if (error) + { + *error = true; + return false; + } + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("timestamp(%d) precision must be between %d and %d", typmod, 0, MAX_TIMESTAMP_PRECISION))); + } if (*time >= INT64CONST(0)) { @@ -383,8 +391,15 @@ AdjustTimestampForTypmod(Timestamp *time, int32 typmod) * TimestampScales[typmod]); } } + + return true; } +void +AdjustTimestampForTypmod(Timestamp *time, int32 typmod) +{ + (void) AdjustTimestampForTypmodError(time, typmod, NULL); +} /* timestamptz_in() * Convert a string to internal form. @@ -5194,8 +5209,8 @@ timestamp_timestamptz(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(timestamp2timestamptz(timestamp)); } -static TimestampTz -timestamp2timestamptz(Timestamp timestamp) +TimestampTz +timestamp2timestamptz_internal(Timestamp timestamp, int *tzp, bool *error) { TimestampTz result; struct pg_tm tt, @@ -5204,23 +5219,30 @@ timestamp2timestamptz(Timestamp timestamp) int tz; if (TIMESTAMP_NOT_FINITE(timestamp)) - result = timestamp; - else - { - if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + return timestamp; - tz = DetermineTimeZoneOffset(tm, session_timezone); + if (!timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL)) + { + tz = tzp ? *tzp : DetermineTimeZoneOffset(tm, session_timezone); - if (tm2timestamp(tm, fsec, &tz, &result) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + if (!tm2timestamp(tm, fsec, &tz, &result)) + return result; } - return result; + if (error) + *error = true; + else + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); + + return 0; +} + +static TimestampTz +timestamp2timestamptz(Timestamp timestamp) +{ + return timestamp2timestamptz_internal(timestamp, NULL, NULL); } /* timestamptz_timestamp() diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index 16f5ca233a..72876533ac 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -207,6 +207,7 @@ Section: Class 22 - Data Exception 2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment 2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction 22030 E ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE duplicate_json_object_key_value +22031 E ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION invalid_argument_for_json_datetime_function 22032 E ERRCODE_INVALID_JSON_TEXT invalid_json_text 22033 E ERRCODE_INVALID_JSON_SUBSCRIPT invalid_json_subscript 22034 E ERRCODE_MORE_THAN_ONE_JSON_ITEM more_than_one_json_item diff --git a/src/include/utils/date.h b/src/include/utils/date.h index bd15bfa5bb..06ebb7dc02 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -70,6 +70,9 @@ typedef struct /* date.c */ extern int32 anytime_typmod_check(bool istz, int32 typmod); extern double date2timestamp_no_overflow(DateADT dateVal); +extern Timestamp date2timestamp_internal(DateADT dateVal, bool *error); +extern TimestampTz date2timestamptz_internal(DateADT dateVal, int *tzp, + bool *error); extern void EncodeSpecialDate(DateADT dt, char *str); extern DateADT GetSQLCurrentDate(void); extern TimeTzADT *GetSQLCurrentTime(int32 typmod); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 3c04e21b79..3f2d32af94 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -339,5 +339,7 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); +extern bool AdjustTimestampForTypmodError(Timestamp *time, int32 typmod, + bool *error); #endif /* DATETIME_H */ diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 227779cf79..c9efdd1f03 100644 --- a/src/include/utils/formatting.h +++ b/src/include/utils/formatting.h @@ -28,7 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes); extern char *asc_toupper(const char *buff, size_t nbytes); extern char *asc_initcap(const char *buff, size_t nbytes); -extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname, - bool strict, Oid *typid, int32 *typmod, int *tz); +extern Datum parse_datetime(text *date_txt, text *fmt_txt, char *tzname, + bool strict, Oid *typid, int32 *typmod, int *tz, bool *error); #endif diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 5f4d479a7b..20918f6251 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -161,6 +161,7 @@ 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 char *JsonEncodeDateTime(char *buf, Datum value, Oid typid); +extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, + const int *tzp); #endif /* JSONAPI_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 2fe7d32fec..6a7a37bad8 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -241,7 +241,15 @@ enum jbvType jbvArray = 0x10, jbvObject, /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ - jbvBinary + jbvBinary, + + /* + * Virtual types. + * + * These types are used only for in-memory JSON processing and serialized + * into JSON strings when outputted to json/jsonb. + */ + jbvDatetime = 0x20, }; /* @@ -282,11 +290,20 @@ struct JsonbValue int len; JsonbContainer *data; } binary; /* Array or object, in on-disk format */ + + struct + { + Datum value; + Oid typid; + int32 typmod; + int tz; + } datetime; } val; }; -#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \ - (jsonbval)->type <= jbvBool) +#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \ + (jsonbval)->type <= jbvBool) || \ + (jsonbval)->type == jbvDatetime) /* * Key/value pair within an Object. diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 40ad5fda92..f6b17c8aa2 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -79,6 +79,7 @@ typedef enum JsonPathItemType jpiFloor, /* .floor() item method */ jpiCeiling, /* .ceiling() item method */ jpiDouble, /* .double() item method */ + jpiDatetime, /* .datetime() item method */ jpiKeyValue, /* .keyvalue() item method */ jpiSubscript, /* array subscript: 'expr' or 'expr TO expr' */ jpiLast, /* LAST array subscript */ diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index ea16190ec3..077835fb1c 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -97,6 +97,9 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); /* timestamp comparison works for timestamptz also */ #define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2) +extern TimestampTz timestamp2timestamptz_internal(Timestamp timestamp, + int *tzp, bool *error); + extern int isoweek2j(int year, int week); extern void isoweek2date(int woy, int *year, int *mon, int *mday); extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 31a871af02..eaf0ee3c6a 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1658,6 +1658,551 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik "a\b" (1 row) +select jsonb_path_query('null', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select jsonb_path_query('true', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select jsonb_path_query('1', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select jsonb_path_query('[]', '$.datetime()'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[]', 'strict $.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select jsonb_path_query('{}', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select jsonb_path_query('""', '$.datetime()'); +ERROR: unrecognized datetime format +HINT: use datetime template argument for explicit format specification +select jsonb_path_query('"12:34"', '$.datetime("aaa")'); +ERROR: datetime format is not dated and not timed +select jsonb_path_query('"12:34"', '$.datetime("aaa", 1)'); +ERROR: datetime format is not dated and not timed +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)'); + jsonb_path_query +--------------------- + "12:34:00+00:00:01" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)'); + jsonb_path_query +------------------------- + "12:34:00+596523:14:07" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); + jsonb_path_query +------------------------- + "12:34:00-596523:14:07" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select jsonb_path_query('"aaaa"', '$.datetime("HH24")'); +ERROR: invalid value "aa" for "HH24" +DETAIL: Value must be an integer. +select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; + ?column? +---------- + t +(1 row) + +select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); + jsonb_path_query +------------------ + "2017-03-10" +(1 row) + +select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); + jsonb_path_query +------------------ + "date" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); + jsonb_path_query +------------------ + "2017-03-10" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + jsonb_path_query +------------------ + "date" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); + jsonb_path_query +------------------------------- + "timestamp without time zone" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); + jsonb_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()'); + jsonb_path_query +-------------------------- + "time without time zone" +(1 row) + +select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + jsonb_path_query +----------------------- + "time with time zone" +(1 row) + +set time zone '+00'; +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + jsonb_path_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timestamptz +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'); + jsonb_path_query +-------------------------------- + "2017-03-10T12:34:00-00:12:34" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'); +ERROR: invalid input syntax for type timestamptz: "UTC" +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))'); + jsonb_path_query +-------------------------------- + "2017-03-10T12:34:00-05:12:34" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + jsonb_build_object('tz', extract(timezone from now()))); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+05:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00-05:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+05:20" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00-05:20" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")'); + jsonb_path_query +------------------ + "12:34:00" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timetz +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")'); + jsonb_path_query +------------------ + "12:34:00+00:00" +(1 row) + +select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); + jsonb_path_query +------------------ + "12:34:00+05:00" +(1 row) + +select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); + jsonb_path_query +------------------ + "12:34:00-05:00" +(1 row) + +select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + jsonb_path_query +------------------ + "12:34:00+05:20" +(1 row) + +select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + jsonb_path_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone '+10'; +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + jsonb_path_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timestamptz +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + jsonb_build_object('tz', extract(timezone from now()))); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+05:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00-05:00" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+05:20" +(1 row) + +select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00-05:20" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")'); + jsonb_path_query +------------------ + "12:34:00" +(1 row) + +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timetz +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")'); + jsonb_path_query +------------------ + "12:34:00+10:00" +(1 row) + +select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); + jsonb_path_query +------------------ + "12:34:00+05:00" +(1 row) + +select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); + jsonb_path_query +------------------ + "12:34:00-05:00" +(1 row) + +select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + jsonb_path_query +------------------ + "12:34:00+05:20" +(1 row) + +select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + jsonb_path_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone default; +select jsonb_path_query('"2017-03-10"', '$.datetime().type()'); + jsonb_path_query +------------------ + "date" +(1 row) + +select jsonb_path_query('"2017-03-10"', '$.datetime()'); + jsonb_path_query +------------------ + "2017-03-10" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()'); + jsonb_path_query +------------------------------- + "timestamp without time zone" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()'); + jsonb_path_query +----------------------- + "2017-03-10T12:34:56" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); + jsonb_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:56+03:00" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); + jsonb_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:56+03:10" +(1 row) + +select jsonb_path_query('"12:34:56"', '$.datetime().type()'); + jsonb_path_query +-------------------------- + "time without time zone" +(1 row) + +select jsonb_path_query('"12:34:56"', '$.datetime()'); + jsonb_path_query +------------------ + "12:34:56" +(1 row) + +select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()'); + jsonb_path_query +----------------------- + "time with time zone" +(1 row) + +select jsonb_path_query('"12:34:56 +3"', '$.datetime()'); + jsonb_path_query +------------------ + "12:34:56+03:00" +(1 row) + +select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()'); + jsonb_path_query +----------------------- + "time with time zone" +(1 row) + +select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()'); + jsonb_path_query +------------------ + "12:34:56+03:10" +(1 row) + +set time zone '+00'; +-- date comparison +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); + jsonb_path_query +----------------------------- + "2017-03-10" + "2017-03-10T00:00:00" + "2017-03-10T03:00:00+03:00" +(3 rows) + +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); + jsonb_path_query +----------------------------- + "2017-03-10" + "2017-03-11" + "2017-03-10T00:00:00" + "2017-03-10T12:34:56" + "2017-03-10T03:00:00+03:00" +(5 rows) + +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); + jsonb_path_query +----------------------------- + "2017-03-09" + "2017-03-10T01:02:03+04:00" +(2 rows) + +-- time comparison +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); + jsonb_path_query +------------------ + "12:35:00" + "12:35:00+00:00" +(2 rows) + +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); + jsonb_path_query +------------------ + "12:35:00" + "12:36:00" + "12:35:00+00:00" +(3 rows) + +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); + jsonb_path_query +------------------ + "12:34:00" + "12:35:00+01:00" + "13:35:00+01:00" +(3 rows) + +-- timetz comparison +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); + jsonb_path_query +------------------ + "12:35:00+01:00" +(1 row) + +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); + jsonb_path_query +------------------ + "12:35:00+01:00" + "12:36:00+01:00" + "12:35:00-02:00" + "11:35:00" + "12:35:00" +(5 rows) + +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); + jsonb_path_query +------------------ + "12:34:00+01:00" + "12:35:00+02:00" + "10:35:00" +(3 rows) + +-- timestamp comparison +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T13:35:00+01:00" +(2 rows) + +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:36:00" + "2017-03-10T13:35:00+01:00" + "2017-03-10T12:35:00-01:00" + "2017-03-11" +(5 rows) + +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00" + "2017-03-10T12:35:00+01:00" + "2017-03-10" +(3 rows) + +-- timestamptz comparison +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:35:00+01:00" + "2017-03-10T11:35:00" +(2 rows) + +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:35:00+01:00" + "2017-03-10T12:36:00+01:00" + "2017-03-10T12:35:00-02:00" + "2017-03-10T11:35:00" + "2017-03-10T12:35:00" + "2017-03-11" +(6 rows) + +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + jsonb_path_query +----------------------------- + "2017-03-10T12:34:00+01:00" + "2017-03-10T12:35:00+02:00" + "2017-03-10T10:35:00" + "2017-03-10" +(4 rows) + +set time zone default; -- jsonpath operators SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); jsonb_path_query diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 51a6b7ac75..fb9fbf2154 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -405,6 +405,24 @@ select '$.keyvalue().key'::jsonpath; $.keyvalue()."key" (1 row) +select '$.datetime()'::jsonpath; + jsonpath +-------------- + $.datetime() +(1 row) + +select '$.datetime("datetime template")'::jsonpath; + jsonpath +--------------------------------- + $.datetime("datetime template") +(1 row) + +select '$.datetime("datetime template", "default timezone")'::jsonpath; + jsonpath +----------------------------------------------------- + $.datetime("datetime template", "default timezone") +(1 row) + select '$ ? (@ starts with "abc")'::jsonpath; jsonpath ------------------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 733fbd4e0d..8d08525a75 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -346,6 +346,154 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\B$" flag "iq")'); select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ like_regex "^a\\b$" flag "")'); +select jsonb_path_query('null', '$.datetime()'); +select jsonb_path_query('true', '$.datetime()'); +select jsonb_path_query('1', '$.datetime()'); +select jsonb_path_query('[]', '$.datetime()'); +select jsonb_path_query('[]', 'strict $.datetime()'); +select jsonb_path_query('{}', '$.datetime()'); +select jsonb_path_query('""', '$.datetime()'); +select jsonb_path_query('"12:34"', '$.datetime("aaa")'); +select jsonb_path_query('"12:34"', '$.datetime("aaa", 1)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); +select jsonb_path_query('"aaaa"', '$.datetime("HH24")'); + +select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; +select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); +select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + +select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); +select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()'); +select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + +set time zone '+00'; + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + jsonb_build_object('tz', extract(timezone from now()))); +select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")'); +select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone '+10'; + +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'); +select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + jsonb_build_object('tz', extract(timezone from now()))); +select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")'); +select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone default; + +select jsonb_path_query('"2017-03-10"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); +select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); +select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); +select jsonb_path_query('"12:34:56"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56"', '$.datetime()'); +select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56 +3"', '$.datetime()'); +select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()'); +select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()'); + +set time zone '+00'; + +-- date comparison +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); +select jsonb_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); + +-- time comparison +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); +select jsonb_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); + +-- timetz comparison +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); +select jsonb_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); + +-- timestamp comparison +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); +select jsonb_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + +-- timestamptz comparison +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); +select jsonb_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + +set time zone default; + -- jsonpath operators SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 7dbfc3bfcf..e65ec0236c 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -73,6 +73,9 @@ select '"aaa".type()'::jsonpath; select 'true.type()'::jsonpath; select '$.double().floor().ceiling().abs()'::jsonpath; select '$.keyvalue().key'::jsonpath; +select '$.datetime()'::jsonpath; +select '$.datetime("datetime template")'::jsonpath; +select '$.datetime("datetime template", "default timezone")'::jsonpath; select '$ ? (@ starts with "abc")'::jsonpath; select '$ ? (@ starts with $var)'::jsonpath; From 30dda92d1401a0c2f6605ae00922e26102a60b11 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 23:42:53 +0300 Subject: [PATCH 06/66] Save default time-zone for non-zoned types in jsonpath --- src/backend/utils/adt/formatting.c | 4 + src/backend/utils/adt/jsonpath_exec.c | 87 +++++--------------- src/test/regress/expected/jsonb_jsonpath.out | 72 ++++++---------- 3 files changed, 50 insertions(+), 113 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index b1ca4d114f..29d753f090 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -4147,6 +4147,10 @@ parse_datetime(text *date_txt, text *fmt, char *tzname, bool strict, error)) return (Datum) 0; + /* Save default time-zone for non-zoned types. */ + if (!(flags & DCH_ZONED) && tzname && + !get_timezone(&tm, tzname, NULL, "timezone", false, tz, error)) + return (Datum) 0; if (flags & DCH_DATED) { diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 732e261bbf..abbbbc7813 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1046,6 +1046,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, int32 typmod = -1; int tz = PG_INT32_MIN; bool hasNext; + char *tzname = NULL; if (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, @@ -1065,7 +1066,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, text *template; char *template_str; int template_len; - char *tzname = NULL; jspGetLeftArg(jsp, &elem); @@ -1113,20 +1113,21 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } } - template = cstring_to_text_with_len(template_str, - template_len); - - if (tryToParseDatetime(template, datetime, tzname, false, - &value, &typid, &typmod, - &tz, jspThrowErrors(cxt))) - res = jperOk; - else - res = jperError; + if (template_len) + { + template = cstring_to_text_with_len(template_str, + template_len); - if (tzname) - pfree(tzname); + if (tryToParseDatetime(template, datetime, tzname, false, + &value, &typid, &typmod, + &tz, jspThrowErrors(cxt))) + res = jperOk; + else + res = jperError; + } } - else + + if (res == jperNotFound) { /* Try to recognize one of ISO formats. */ static const char *fmt_str[] = @@ -1155,7 +1156,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, MemoryContextSwitchTo(oldcxt); } - if (tryToParseDatetime(fmt_txt[i], datetime, NULL, + if (tryToParseDatetime(fmt_txt[i], datetime, tzname, true, &value, &typid, &typmod, &tz, false)) { @@ -1171,6 +1172,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, errhint("use datetime template argument for explicit format specification")))); } + if (tzname) + pfree(tzname); + pfree(datetime); if (jperIsError(res)) @@ -2459,18 +2463,6 @@ wrapItemsInArray(const JsonValueList *items) return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } -#define USE_CURRENT_TZ - -static int -get_current_tz() -{ - struct pg_tm tm; - - GetCurrentDateTime(&tm); - - return DetermineTimeZoneOffset(&tm, session_timezone); -} - static inline Datum time_to_timetz(Datum time, int tz, bool *error) { @@ -2479,12 +2471,8 @@ time_to_timetz(Datum time, int tz, bool *error) if (tz == PG_INT32_MIN) { -#ifdef USE_CURRENT_TZ - tz = get_current_tz(); -#else *error = true; return (Datum) 0; -#endif } result->time = tm; @@ -2510,12 +2498,8 @@ date_to_timestamptz(Datum date, int tz, bool *error) if (tz == PG_INT32_MIN) { -#ifdef USE_CURRENT_TZ - tz = get_current_tz(); -#else *error = true; return (Datum) 0; -#endif } ts = date2timestamptz_internal(dt, &tz, error); @@ -2531,12 +2515,8 @@ timestamp_to_timestamptz(Datum val, int tz, bool *error) if (tz == PG_INT32_MIN) { -#ifdef USE_CURRENT_TZ - tz = get_current_tz(); -#else *error = true; return (Datum) 0; -#endif } tstz = timestamp2timestamptz_internal(ts, &tz, error); @@ -2713,35 +2693,8 @@ tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, bool error = false; int tz = *tzp; -#if 0 - if (throwErrors) - { - PG_TRY(); - { - *value = parse_datetime(datetime, fmt, tzname, strict, typid, - typmod, &tz, throwErrors ? NULL : &error); - } - PG_CATCH(); - { - if (/* ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION */ - geterrcode() == ERRCODE_INVALID_DATETIME_FORMAT || - geterrcode() == ERRCODE_DATETIME_VALUE_OUT_OF_RANGE) - { - /* - * Save original datetime error message, details and hint, just - * replace errcode with a SQL/JSON one. - */ - errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); - } - PG_RE_THROW(); - } - PG_END_TRY(); - } - else -#endif - - *value = parse_datetime(datetime, fmt, tzname, strict, typid, typmod, - &tz, throwErrors ? NULL : &error); + *value = parse_datetime(datetime, fmt, tzname, strict, typid, typmod, + &tz, throwErrors ? NULL : &error); if (!error) *tzp = tz; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index eaf0ee3c6a..9570a6ac81 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2047,33 +2047,30 @@ set time zone '+00'; select jsonb_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +----------------------- "2017-03-10" "2017-03-10T00:00:00" - "2017-03-10T03:00:00+03:00" -(3 rows) +(2 rows) select jsonb_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +----------------------- "2017-03-10" "2017-03-11" "2017-03-10T00:00:00" "2017-03-10T12:34:56" - "2017-03-10T03:00:00+03:00" -(5 rows) +(4 rows) select jsonb_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +------------------ "2017-03-09" - "2017-03-10T01:02:03+04:00" -(2 rows) +(1 row) -- time comparison select jsonb_path_query( @@ -2082,8 +2079,7 @@ select jsonb_path_query( jsonb_path_query ------------------ "12:35:00" - "12:35:00+00:00" -(2 rows) +(1 row) select jsonb_path_query( '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', @@ -2092,8 +2088,7 @@ select jsonb_path_query( ------------------ "12:35:00" "12:36:00" - "12:35:00+00:00" -(3 rows) +(2 rows) select jsonb_path_query( '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', @@ -2101,9 +2096,7 @@ select jsonb_path_query( jsonb_path_query ------------------ "12:34:00" - "12:35:00+01:00" - "13:35:00+01:00" -(3 rows) +(1 row) -- timetz comparison select jsonb_path_query( @@ -2122,9 +2115,7 @@ select jsonb_path_query( "12:35:00+01:00" "12:36:00+01:00" "12:35:00-02:00" - "11:35:00" - "12:35:00" -(5 rows) +(3 rows) select jsonb_path_query( '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', @@ -2133,40 +2124,35 @@ select jsonb_path_query( ------------------ "12:34:00+01:00" "12:35:00+02:00" - "10:35:00" -(3 rows) +(2 rows) -- timestamp comparison select jsonb_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +----------------------- "2017-03-10T12:35:00" - "2017-03-10T13:35:00+01:00" -(2 rows) +(1 row) select jsonb_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +----------------------- "2017-03-10T12:35:00" "2017-03-10T12:36:00" - "2017-03-10T13:35:00+01:00" - "2017-03-10T12:35:00-01:00" "2017-03-11" -(5 rows) +(3 rows) select jsonb_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - jsonb_path_query ------------------------------ + jsonb_path_query +----------------------- "2017-03-10T12:34:00" - "2017-03-10T12:35:00+01:00" "2017-03-10" -(3 rows) +(2 rows) -- timestamptz comparison select jsonb_path_query( @@ -2175,8 +2161,7 @@ select jsonb_path_query( jsonb_path_query ----------------------------- "2017-03-10T12:35:00+01:00" - "2017-03-10T11:35:00" -(2 rows) +(1 row) select jsonb_path_query( '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', @@ -2186,10 +2171,7 @@ select jsonb_path_query( "2017-03-10T12:35:00+01:00" "2017-03-10T12:36:00+01:00" "2017-03-10T12:35:00-02:00" - "2017-03-10T11:35:00" - "2017-03-10T12:35:00" - "2017-03-11" -(6 rows) +(3 rows) select jsonb_path_query( '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', @@ -2198,9 +2180,7 @@ select jsonb_path_query( ----------------------------- "2017-03-10T12:34:00+01:00" "2017-03-10T12:35:00+02:00" - "2017-03-10T10:35:00" - "2017-03-10" -(4 rows) +(2 rows) set time zone default; -- jsonpath operators From c009b4e5061ecf2306385c3fa831e8952fd7f642 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 18:08:13 +0300 Subject: [PATCH 07/66] Add stack for jsonpath @N execution --- src/backend/utils/adt/jsonpath_exec.c | 48 +++++++++++++++++++++++---- src/tools/pgindent/typedefs.list | 2 ++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index abbbbc7813..114ab96a02 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -87,6 +87,19 @@ typedef struct JsonBaseObjectInfo int id; } JsonBaseObjectInfo; +/* + * Special data structure representing stack of current items. We use it + * instead of regular list in order to evade extra memory allocation. These + * items are always allocated in local variables. + */ +typedef struct JsonItemStackEntry +{ + JsonbValue *item; + struct JsonItemStackEntry *parent; +} JsonItemStackEntry; + +typedef JsonItemStackEntry *JsonItemStack; + /* * Context of jsonpath execution. */ @@ -94,7 +107,7 @@ typedef struct JsonPathExecContext { Jsonb *vars; /* variables to substitute into jsonpath */ JsonbValue *root; /* for $ evaluation */ - JsonbValue *current; /* for @ evaluation */ + JsonItemStack stack; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() * evaluation */ int lastGeneratedObjectId; /* "id" counter for .keyvalue() @@ -256,6 +269,10 @@ static int compareDatetime(Datum val1, Oid typid1, int tz1, Datum val2, Oid typid2, int tz2, bool *error); +static void pushJsonItem(JsonItemStack *stack, + JsonItemStackEntry *entry, JsonbValue *item); +static void popJsonItem(JsonItemStack *stack); + /****************** User interface to JsonPath executor ********************/ /* @@ -485,6 +502,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, JsonPathExecResult res; JsonPathItem jsp; JsonbValue jbv; + JsonItemStackEntry root; jspInit(&jsp, path); @@ -503,13 +521,15 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors, cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = &jbv; - cxt.current = &jbv; + cxt.stack = NULL; cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; cxt.lastGeneratedObjectId = vars ? 2 : 1; cxt.innermostArraySize = -1; cxt.throwErrors = throwErrors; + pushJsonItem(&cxt.stack, &root, cxt.root); + if (jspStrictAbsenseOfErrors(&cxt) && !result) { /* @@ -636,7 +656,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiCurrent: - res = executeNextItem(cxt, jsp, NULL, cxt->current, + res = executeNextItem(cxt, jsp, NULL, cxt->stack->item, found, true); break; @@ -1453,13 +1473,12 @@ static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { - JsonbValue *prev; + JsonItemStackEntry current; JsonPathBool res; - prev = cxt->current; - cxt->current = jb; + pushJsonItem(&cxt->stack, ¤t, jb); res = executeBoolItem(cxt, jsp, jb, false); - cxt->current = prev; + popJsonItem(&cxt->stack); return res; } @@ -2463,6 +2482,21 @@ wrapItemsInArray(const JsonValueList *items) return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } +static void +pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, + JsonbValue *item) +{ + entry->item = item; + entry->parent = *stack; + *stack = entry; +} + +static void +popJsonItem(JsonItemStack *stack) +{ + *stack = (*stack)->parent; +} + static inline Datum time_to_timetz(Datum time, int tz, bool *error) { diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index bdcbc8d15e..adccd6f8d8 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1121,6 +1121,8 @@ JsValue JsonAggState JsonBaseObjectInfo JsonHashEntry +JsonItemStack +JsonItemStackEntry JsonIterateStringValuesAction JsonLexContext JsonLikeRegexContext From aa10d33308efd71ee21e033c07b2061f2fafe2c6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 23:30:36 +0300 Subject: [PATCH 08/66] Refactor jsonpath variables execution --- src/backend/utils/adt/jsonpath_exec.c | 111 ++++++++++++++++---------- 1 file changed, 70 insertions(+), 41 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 114ab96a02..a176b6c2f3 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -100,12 +100,16 @@ typedef struct JsonItemStackEntry typedef JsonItemStackEntry *JsonItemStack; +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 */ JsonItemStack stack; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() @@ -185,8 +189,10 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); -static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars, - Jsonb *json, bool throwErrors, JsonValueList *result); +static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, + JsonPathVarCallback getVar, + Jsonb *json, bool throwErrors, + JsonValueList *result); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt, @@ -236,7 +242,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); @@ -302,7 +311,8 @@ jsonb_path_exists(PG_FUNCTION_ARGS) silent = PG_GETARG_BOOL(3); } - res = executeJsonPath(jp, vars, jb, !silent, NULL); + res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, NULL); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -345,7 +355,8 @@ jsonb_path_match(PG_FUNCTION_ARGS) silent = PG_GETARG_BOOL(3); } - (void) executeJsonPath(jp, vars, jb, !silent, &found); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -411,7 +422,8 @@ jsonb_path_query(PG_FUNCTION_ARGS) vars = PG_GETARG_JSONB_P_COPY(2); silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found); funcctx->user_fctx = JsonValueListGetList(&found); @@ -446,7 +458,8 @@ jsonb_path_query_array(PG_FUNCTION_ARGS) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found); PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); } @@ -465,7 +478,8 @@ jsonb_path_query_first(PG_FUNCTION_ARGS) Jsonb *vars = PG_GETARG_JSONB_P(2); bool silent = PG_GETARG_BOOL(3); - (void) executeJsonPath(jp, vars, jb, !silent, &found); + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found); if (JsonValueListLength(&found) >= 1) PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); @@ -495,8 +509,8 @@ jsonb_path_query_first(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) +executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, + Jsonb *json, bool throwErrors, JsonValueList *result) { JsonPathExecContext cxt; JsonPathExecResult res; @@ -509,22 +523,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.stack = NULL; 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; @@ -2085,7 +2093,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"); @@ -2097,42 +2105,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; + + *value = *v; + pfree(v); - JsonbInitBinary(&tmp, vars); - setBaseObject(cxt, &tmp, 1); + JsonbInitBinary(baseObject, vars); + return 1; } /**************** Support functions for JsonPath execution *****************/ From 30ff7c2e6e2c349d6667725e3b7b83909ef23606 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 23:39:50 +0300 Subject: [PATCH 09/66] Add jsonb_path_query_first_text() --- src/backend/catalog/system_views.sql | 8 +++ src/backend/utils/adt/jsonpath_exec.c | 87 +++++++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 4 ++ 3 files changed, 99 insertions(+) diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index ea4c85e395..6af68d0d72 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1287,6 +1287,14 @@ LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE AS 'jsonb_path_query_first'; +CREATE OR REPLACE FUNCTION + jsonb_path_query_first_text(target jsonb, path jsonpath, vars jsonb DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS text +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'jsonb_path_query_first_text'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a176b6c2f3..8202286a06 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -72,6 +72,7 @@ #include "utils/float.h" #include "utils/guc.h" #include "utils/json.h" +#include "utils/jsonapi.h" #include "utils/jsonpath.h" #include "utils/date.h" #include "utils/timestamp.h" @@ -270,6 +271,7 @@ static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); static int JsonbType(JsonbValue *jb); static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items); +static text *JsonbValueUnquoteText(JsonbValue *jbv); static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, @@ -487,6 +489,29 @@ jsonb_path_query_first(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +/* + * jsonb_path_query_first_text + * Executes jsonpath for given jsonb document and returns first result + * item as text. If there are no items, NULL returned. + */ +Datum +jsonb_path_query_first_text(FunctionCallInfo fcinfo) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + Jsonb *vars = PG_GETARG_JSONB_P(2); + bool silent = PG_GETARG_BOOL(3); + JsonValueList found = {0}; + + (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, + jb, !silent, &found); + + if (JsonValueListLength(&found) >= 1) + PG_RETURN_TEXT_P(JsonbValueUnquoteText(JsonValueListHead(&found))); + else + PG_RETURN_NULL(); +} + /********************Execute functions for JsonPath**************************/ /* @@ -2483,6 +2508,68 @@ JsonbType(JsonbValue *jb) return type; } +/* + * Convert jsonb to a C-string stripping quotes from scalar strings. + */ +static char * +JsonbValueUnquote(JsonbValue *jbv, int *len) +{ + switch (jbv->type) + { + case jbvString: + *len = jbv->val.string.len; + return jbv->val.string.val; + + case jbvBool: + *len = jbv->val.boolean ? 4 : 5; + return jbv->val.boolean ? "true" : "false"; + + case jbvNumeric: + *len = -1; + return DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(jbv->val.numeric))); + + case jbvNull: + *len = 4; + return "null"; + + case jbvDatetime: + *len = -1; + return JsonEncodeDateTime(NULL, + jbv->val.datetime.value, + jbv->val.datetime.typid, + &jbv->val.datetime.tz); + + case jbvBinary: + { + JsonbValue jbvbuf; + + if (JsonbExtractScalar(jbv->val.binary.data, &jbvbuf)) + return JsonbValueUnquote(&jbvbuf, len); + + *len = -1; + return JsonbToCString(NULL, jbv->val.binary.data, + jbv->val.binary.len); + } + + default: + elog(ERROR, "unexpected jsonb value type: %d", jbv->type); + return NULL; + } +} + +static text * +JsonbValueUnquoteText(JsonbValue *jbv) +{ + int len; + char *str = JsonbValueUnquote(jbv, &len); + + if (len < 0) + return cstring_to_text(str); + else + return cstring_to_text_with_len(str, len); +} + /* Get scalar of given type or NULL on type mismatch */ static JsonbValue * getScalar(JsonbValue *scalar, enum jbvType type) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 87335248a0..ae879d50ad 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9275,6 +9275,10 @@ proname => 'jsonb_path_query_first', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_query_first' }, +{ oid => '6127', descr => 'jsonpath query first item text', + proname => 'jsonb_path_query_first_text', prorettype => 'text', + proargtypes => 'jsonb jsonpath jsonb bool', + prosrc => 'jsonb_path_query_first_text' }, { oid => '4009', descr => 'jsonpath match', proname => 'jsonb_path_match', prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb bool', prosrc => 'jsonb_path_match' }, From d8025e9da62c47f717144ca96486046fbdd52296 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 23 Jan 2019 17:07:12 +0300 Subject: [PATCH 10/66] Extract common code from jsonb_path_*() functions --- src/backend/utils/adt/jsonpath_exec.c | 198 ++++++++++++++++---------- src/tools/pgindent/typedefs.list | 4 + 2 files changed, 128 insertions(+), 74 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 8202286a06..a800a2849e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -168,6 +168,20 @@ typedef struct JsonValueListIterator ListCell *next; } JsonValueListIterator; +/* + * Context for execution of + * jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent boolean]) user functions. + */ +typedef struct JsonPathUserFuncContext +{ + FunctionCallInfo fcinfo; + Jsonb *jb; /* first jsonb function argument */ + JsonPath *jp; /* second jsonpath function argument */ + Jsonb *vars; /* third vars function argument */ + JsonValueList found; /* resulting item list */ + bool silent; /* error suppression flag */ +} JsonPathUserFuncContext; + /* strict/lax flags is decomposed into four [un]wrap/error flags */ #define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode) #define jspAutoUnwrap(cxt) ((cxt)->laxMode) @@ -190,6 +204,10 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); +static void freeUserFuncContext(JsonPathUserFuncContext *cxt); +static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, + JsonPathUserFuncContext *cxt, bool copy); + static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, Jsonb *json, bool throwErrors, @@ -301,23 +319,7 @@ static void popJsonItem(JsonItemStack *stack); Datum jsonb_path_exists(PG_FUNCTION_ARGS) { - Jsonb *jb = PG_GETARG_JSONB_P(0); - JsonPath *jp = PG_GETARG_JSONPATH_P(1); - JsonPathExecResult res; - Jsonb *vars = NULL; - bool silent = true; - - if (PG_NARGS() == 4) - { - vars = PG_GETARG_JSONB_P(2); - silent = PG_GETARG_BOOL(3); - } - - res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, NULL); - - PG_FREE_IF_COPY(jb, 0); - PG_FREE_IF_COPY(jp, 1); + JsonPathExecResult res = executeUserFunc(fcinfo, NULL, false); if (jperIsError(res)) PG_RETURN_NULL(); @@ -345,27 +347,15 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS) Datum jsonb_path_match(PG_FUNCTION_ARGS) { - Jsonb *jb = PG_GETARG_JSONB_P(0); - JsonPath *jp = PG_GETARG_JSONPATH_P(1); - JsonValueList found = {0}; - Jsonb *vars = NULL; - bool silent = true; + JsonPathUserFuncContext cxt; - if (PG_NARGS() == 4) - { - vars = PG_GETARG_JSONB_P(2); - silent = PG_GETARG_BOOL(3); - } + (void) executeUserFunc(fcinfo, &cxt, false); - (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, &found); + freeUserFuncContext(&cxt); - PG_FREE_IF_COPY(jb, 0); - PG_FREE_IF_COPY(jp, 1); - - if (JsonValueListLength(&found) == 1) + if (JsonValueListLength(&cxt.found) == 1) { - JsonbValue *jbv = JsonValueListHead(&found); + JsonbValue *jbv = JsonValueListHead(&cxt.found); if (jbv->type == jbvBool) PG_RETURN_BOOL(jbv->val.boolean); @@ -374,7 +364,7 @@ jsonb_path_match(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - if (!silent) + if (!cxt.silent) ereport(ERROR, (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), errmsg("single boolean result is expected"))); @@ -409,26 +399,21 @@ jsonb_path_query(PG_FUNCTION_ARGS) if (SRF_IS_FIRSTCALL()) { - JsonPath *jp; - Jsonb *jb; + JsonPathUserFuncContext jspcxt; MemoryContext oldcontext; - Jsonb *vars; - bool silent; - JsonValueList found = {0}; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - jb = PG_GETARG_JSONB_P_COPY(0); - jp = PG_GETARG_JSONPATH_P_COPY(1); - vars = PG_GETARG_JSONB_P_COPY(2); - silent = PG_GETARG_BOOL(3); - - (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, &found); + /* jsonb and jsonpath arguments are copied into SRF context. */ + (void) executeUserFunc(fcinfo, &jspcxt, true); - funcctx->user_fctx = JsonValueListGetList(&found); + /* + * Don't free jspcxt because items in jspcxt.found can reference + * untoasted copies of jsonb and jsonpath arguments. + */ + funcctx->user_fctx = JsonValueListGetList(&jspcxt.found); MemoryContextSwitchTo(oldcontext); } @@ -454,16 +439,16 @@ jsonb_path_query(PG_FUNCTION_ARGS) Datum jsonb_path_query_array(PG_FUNCTION_ARGS) { - Jsonb *jb = PG_GETARG_JSONB_P(0); - JsonPath *jp = PG_GETARG_JSONPATH_P(1); - JsonValueList found = {0}; - Jsonb *vars = PG_GETARG_JSONB_P(2); - bool silent = PG_GETARG_BOOL(3); + JsonPathUserFuncContext cxt; + Jsonb *jb; + + (void) executeUserFunc(fcinfo, &cxt, false); + + jb = JsonbValueToJsonb(wrapItemsInArray(&cxt.found)); - (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, &found); + freeUserFuncContext(&cxt); - PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); + PG_RETURN_JSONB_P(jb); } /* @@ -474,17 +459,20 @@ jsonb_path_query_array(PG_FUNCTION_ARGS) Datum jsonb_path_query_first(PG_FUNCTION_ARGS) { - Jsonb *jb = PG_GETARG_JSONB_P(0); - JsonPath *jp = PG_GETARG_JSONPATH_P(1); - JsonValueList found = {0}; - Jsonb *vars = PG_GETARG_JSONB_P(2); - bool silent = PG_GETARG_BOOL(3); + JsonPathUserFuncContext cxt; + Jsonb *jb; + + (void) executeUserFunc(fcinfo, &cxt, false); + + if (JsonValueListLength(&cxt.found) >= 1) + jb = JsonbValueToJsonb(JsonValueListHead(&cxt.found)); + else + jb = NULL; - (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, &found); + freeUserFuncContext(&cxt); - if (JsonValueListLength(&found) >= 1) - PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); + if (jb) + PG_RETURN_JSONB_P(jb); else PG_RETURN_NULL(); } @@ -497,21 +485,83 @@ jsonb_path_query_first(PG_FUNCTION_ARGS) Datum jsonb_path_query_first_text(FunctionCallInfo fcinfo) { - Jsonb *jb = PG_GETARG_JSONB_P(0); - JsonPath *jp = PG_GETARG_JSONPATH_P(1); - Jsonb *vars = PG_GETARG_JSONB_P(2); - bool silent = PG_GETARG_BOOL(3); - JsonValueList found = {0}; + JsonPathUserFuncContext cxt; + text *txt; - (void) executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, - jb, !silent, &found); + (void) executeUserFunc(fcinfo, &cxt, false); - if (JsonValueListLength(&found) >= 1) - PG_RETURN_TEXT_P(JsonbValueUnquoteText(JsonValueListHead(&found))); + if (JsonValueListLength(&cxt.found) >= 1) + txt = JsonbValueUnquoteText(JsonValueListHead(&cxt.found)); + else + txt = NULL; + + freeUserFuncContext(&cxt); + + if (txt) + PG_RETURN_TEXT_P(txt); else PG_RETURN_NULL(); } +/* Free untoasted copies of jsonb and jsonpath arguments. */ +static void +freeUserFuncContext(JsonPathUserFuncContext *cxt) +{ + FunctionCallInfo fcinfo = cxt->fcinfo; + + PG_FREE_IF_COPY(cxt->jb, 0); + PG_FREE_IF_COPY(cxt->jp, 1); + if (cxt->vars) + PG_FREE_IF_COPY(cxt->vars, 2); +} + +/* + * Common code for jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent bool]) + * user functions. + * + * 'copy' flag enables copying of first three arguments into the current memory + * context. + */ +static JsonPathExecResult +executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, + bool copy) +{ + Jsonb *jb = copy ? PG_GETARG_JSONB_P_COPY(0) : PG_GETARG_JSONB_P(0); + JsonPath *jp = copy ? PG_GETARG_JSONPATH_P_COPY(1) : PG_GETARG_JSONPATH_P(1); + Jsonb *vars = NULL; + bool silent = true; + JsonPathExecResult res; + + if (PG_NARGS() == 4) + { + vars = copy ? PG_GETARG_JSONB_P_COPY(2) : PG_GETARG_JSONB_P(2); + silent = PG_GETARG_BOOL(3); + } + + if (cxt) + { + cxt->fcinfo = fcinfo; + cxt->jb = jb; + cxt->jp = jp; + cxt->vars = vars; + cxt->silent = silent; + memset(&cxt->found, 0, sizeof(cxt->found)); + } + + res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, jb, + !silent, cxt ? &cxt->found : NULL); + + if (!cxt && !copy) + { + PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(jp, 1); + if (vars) + PG_FREE_IF_COPY(vars, 2); + } + + return res; +} + /********************Execute functions for JsonPath**************************/ /* diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index adccd6f8d8..32af37002e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1144,7 +1144,11 @@ JsonPathKeyword JsonPathParseItem JsonPathParseResult JsonPathPredicateCallback +<<<<<<< HEAD JsonPathString +======= +JsonPathUserFuncContext +>>>>>>> 19911df... Extract common code from jsonb_path_*() functions JsonSemAction JsonTokenType JsonTransformStringValuesAction From c3ea75ff24ecd50f0333cd5e05a96ba26e9324d4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 31 Jan 2019 19:53:49 +0300 Subject: [PATCH 11/66] Introduce union JsonItem for jsonpath execution --- src/backend/utils/adt/jsonpath_exec.c | 469 +++++++++++++++----------- src/tools/pgindent/typedefs.list | 1 + 2 files changed, 266 insertions(+), 204 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a800a2849e..829ec2c32f 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -79,6 +79,15 @@ #include "utils/varlena.h" +/* SQL/JSON item */ +typedef union JsonItem +{ + JsonbValue jbv; + enum jbvType type; +} JsonItem; + +#define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) + /* * Represents "base object" and it's "id" for .keyvalue() evaluation. */ @@ -95,14 +104,14 @@ typedef struct JsonBaseObjectInfo */ typedef struct JsonItemStackEntry { - JsonbValue *item; + JsonItem *item; struct JsonItemStackEntry *parent; } JsonItemStackEntry; typedef JsonItemStackEntry *JsonItemStack; typedef int (*JsonPathVarCallback) (void *vars, char *varName, int varNameLen, - JsonbValue *val, JsonbValue *baseObject); + JsonItem *val, JsonbValue *baseObject); /* * Context of jsonpath execution. @@ -111,7 +120,7 @@ typedef struct JsonPathExecContext { void *vars; /* variables to substitute into jsonpath */ JsonPathVarCallback getVar; - JsonbValue *root; /* for $ evaluation */ + JsonItem *root; /* for $ evaluation */ JsonItemStack stack; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() * evaluation */ @@ -154,17 +163,17 @@ typedef enum JsonPathExecResult #define jperIsError(jper) ((jper) == jperError) /* - * List of jsonb values with shortcut for single-value list. + * List of SQL/JSON items with shortcut for single-value list. */ typedef struct JsonValueList { - JsonbValue *singleton; + JsonItem *singleton; List *list; } JsonValueList; typedef struct JsonValueListIterator { - JsonbValue *value; + JsonItem *value; ListCell *next; } JsonValueListIterator; @@ -199,8 +208,8 @@ do { \ } while (0) typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, - JsonbValue *larg, - JsonbValue *rarg, + JsonItem *larg, + JsonItem *rarg, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); @@ -213,83 +222,104 @@ static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, Jsonb *json, bool throwErrors, JsonValueList *result); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); + JsonPathItem *jsp, JsonItem *jb, + JsonValueList *found); static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, - JsonValueList *found, bool unwrap); + JsonPathItem *jsp, + JsonItem *jb, + JsonValueList *found, + bool unwrap); static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, - JsonValueList *found, bool unwrapElements); + JsonPathItem *jsp, + JsonItem *jb, + JsonValueList *found, + bool unwrapElements); static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt, JsonPathItem *cur, JsonPathItem *next, - JsonbValue *v, JsonValueList *found, bool copy); -static JsonPathExecResult executeItemOptUnwrapResult( - JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - bool unwrap, JsonValueList *found); -static JsonPathExecResult executeItemOptUnwrapResultNoThrow( - JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, bool unwrap, JsonValueList *found); -static JsonPathBool executeBoolItem(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext); + JsonItem *v, JsonValueList *found, + bool copy); +static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, + JsonPathItem *jsp, + JsonItem *jb, bool unwrap, + JsonValueList *found); +static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, + JsonPathItem *jsp, + JsonItem *jb, + bool unwrap, + JsonValueList *found); +static JsonPathBool executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, bool canHaveNext); static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb); + JsonPathItem *jsp, JsonItem *jb); static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found, uint32 level, uint32 first, uint32 last, bool ignoreStructuralErrors, bool unwrapNext); static JsonPathBool executePredicate(JsonPathExecContext *cxt, - JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg, - JsonbValue *jb, bool unwrapRightArg, - JsonPathPredicateCallback exec, void *param); + JsonPathItem *pred, JsonPathItem *larg, + JsonPathItem *rarg, JsonItem *jb, + bool unwrapRightArg, + JsonPathPredicateCallback exec, + void *param); static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, - BinaryArithmFunc func, JsonValueList *found); + JsonPathItem *jsp, + JsonItem *jb, + BinaryArithmFunc func, + JsonValueList *found); static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, PGFunction func, + JsonPathItem *jsp, + JsonItem *jb, PGFunction func, JsonValueList *found); -static JsonPathBool executeStartsWith(JsonPathItem *jsp, - JsonbValue *whole, JsonbValue *initial, void *param); -static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, - JsonbValue *rarg, void *param); +static JsonPathBool executeStartsWith(JsonPathItem *jsp, JsonItem *whole, + JsonItem *initial, void *param); +static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonItem *str, + JsonItem *rarg, void *param); static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func, + JsonPathItem *jsp, + JsonItem *jb, bool unwrap, + PGFunction func, JsonValueList *found); static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); + JsonPathItem *jsp, JsonItem *jb, + JsonValueList *found); static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList *found, JsonPathBool res); static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, - JsonbValue *value); + JsonItem *value); static void getJsonPathVariable(JsonPathExecContext *cxt, - JsonPathItem *variable, JsonbValue *value); + JsonPathItem *variable, JsonItem *value); static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, - int varNameLen, JsonbValue *val, + int varNameLen, JsonItem *val, JsonbValue *baseObject); -static int JsonbArraySize(JsonbValue *jb); -static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv, - JsonbValue *rv, void *p); -static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2); +static int JsonbArraySize(JsonItem *jb); +static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, + JsonItem *rv, void *p); +static JsonPathBool compareItems(int32 op, JsonItem *jb1, JsonItem *jb2); static int compareNumeric(Numeric a, Numeric b); -static JsonbValue *copyJsonbValue(JsonbValue *src); +static JsonItem *copyJsonItem(JsonItem *src); +static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); +static Jsonb *JsonItemToJsonb(JsonItem *jsi); +static const char *JsonItemTypeName(JsonItem *jsi); static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, int32 *index); + JsonPathItem *jsp, JsonItem *jb, + int32 *index); static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt, - JsonbValue *jbv, int32 id); -static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv); + JsonItem *jsi, int32 id); +static void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv); static int JsonValueListLength(const JsonValueList *jvl); static bool JsonValueListIsEmpty(JsonValueList *jvl); -static JsonbValue *JsonValueListHead(JsonValueList *jvl); +static JsonItem *JsonValueListHead(JsonValueList *jvl); static List *JsonValueListGetList(JsonValueList *jvl); static void JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it); -static JsonbValue *JsonValueListNext(const JsonValueList *jvl, - JsonValueListIterator *it); -static int JsonbType(JsonbValue *jb); +static JsonItem *JsonValueListNext(const JsonValueList *jvl, + JsonValueListIterator *it); +static int JsonbType(JsonItem *jb); static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); -static int JsonbType(JsonbValue *jb); -static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type); +static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items); static text *JsonbValueUnquoteText(JsonbValue *jbv); +static text *JsonItemUnquoteText(JsonItem *jbv); static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, @@ -299,7 +329,7 @@ static int compareDatetime(Datum val1, Oid typid1, int tz1, bool *error); static void pushJsonItem(JsonItemStack *stack, - JsonItemStackEntry *entry, JsonbValue *item); + JsonItemStackEntry *entry, JsonItem *item); static void popJsonItem(JsonItemStack *stack); /****************** User interface to JsonPath executor ********************/ @@ -355,12 +385,12 @@ jsonb_path_match(PG_FUNCTION_ARGS) if (JsonValueListLength(&cxt.found) == 1) { - JsonbValue *jbv = JsonValueListHead(&cxt.found); + JsonItem *jsi = JsonValueListHead(&cxt.found); - if (jbv->type == jbvBool) - PG_RETURN_BOOL(jbv->val.boolean); + if (jsi->type == jbvBool) + PG_RETURN_BOOL(jsi->jbv.val.boolean); - if (jbv->type == jbvNull) + if (jsi->type == jbvNull) PG_RETURN_NULL(); } @@ -394,7 +424,7 @@ jsonb_path_query(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; List *found; - JsonbValue *v; + JsonItem *v; ListCell *c; if (SRF_IS_FIRSTCALL()) @@ -428,7 +458,7 @@ jsonb_path_query(PG_FUNCTION_ARGS) v = lfirst(c); funcctx->user_fctx = list_delete_first(found); - SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v))); + SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonItemToJsonb(v))); } /* @@ -465,7 +495,7 @@ jsonb_path_query_first(PG_FUNCTION_ARGS) (void) executeUserFunc(fcinfo, &cxt, false); if (JsonValueListLength(&cxt.found) >= 1) - jb = JsonbValueToJsonb(JsonValueListHead(&cxt.found)); + jb = JsonItemToJsonb(JsonValueListHead(&cxt.found)); else jb = NULL; @@ -491,7 +521,7 @@ jsonb_path_query_first_text(FunctionCallInfo fcinfo) (void) executeUserFunc(fcinfo, &cxt, false); if (JsonValueListLength(&cxt.found) >= 1) - txt = JsonbValueUnquoteText(JsonValueListHead(&cxt.found)); + txt = JsonItemUnquoteText(JsonValueListHead(&cxt.found)); else txt = NULL; @@ -590,19 +620,19 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, JsonPathExecContext cxt; JsonPathExecResult res; JsonPathItem jsp; - JsonbValue jbv; + JsonItem jsi; JsonItemStackEntry root; jspInit(&jsp, path); - if (!JsonbExtractScalar(&json->root, &jbv)) - JsonbInitBinary(&jbv, json); + if (!JsonbExtractScalar(&json->root, &jsi.jbv)) + JsonbInitBinary(&jsi.jbv, json); cxt.vars = vars; cxt.getVar = getVar; cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; - cxt.root = &jbv; + cxt.root = &jsi; cxt.stack = NULL; cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; @@ -621,7 +651,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, */ JsonValueList vals = {0}; - res = executeItem(&cxt, &jsp, &jbv, &vals); + res = executeItem(&cxt, &jsp, &jsi, &vals); if (jperIsError(res)) return res; @@ -629,7 +659,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; } - res = executeItem(&cxt, &jsp, &jbv, result); + res = executeItem(&cxt, &jsp, &jsi, result); Assert(!throwErrors || !jperIsError(res)); @@ -641,7 +671,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, */ static JsonPathExecResult executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found) + JsonItem *jb, JsonValueList *found) { return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt)); } @@ -653,7 +683,7 @@ executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found, bool unwrap) + JsonItem *jb, JsonValueList *found, bool unwrap) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -694,13 +724,13 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, key.type = jbvString; key.val.string.val = jspGetString(jsp, &key.val.string.len); - v = findJsonbValueFromContainer(jb->val.binary.data, + v = findJsonbValueFromContainer(jb->jbv.val.binary.data, JB_FOBJECT, &key); if (v != NULL) { - res = executeNextItem(cxt, jsp, NULL, - v, found, false); + jb = JsonbValueToJsonItem(v); + res = executeNextItem(cxt, jsp, NULL, jb, found, false); /* free value if it was not added to found list */ if (jspHasNext(jsp) || !found) @@ -816,29 +846,31 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, for (index = index_from; index <= index_to; index++) { - JsonbValue *v; + JsonItem *jsi; bool copy; if (singleton) { - v = jb; + jsi = jb; copy = true; } else { - v = getIthJsonbValueFromContainer(jb->val.binary.data, - (uint32) index); + JsonbValue *v = + getIthJsonbValueFromContainer(jb->jbv.val.binary.data, + (uint32) index); if (v == NULL) continue; + jsi = JsonbValueToJsonItem(v); copy = false; } if (!hasNext && !found) return jperOk; - res = executeNextItem(cxt, jsp, &elem, v, found, + res = executeNextItem(cxt, jsp, &elem, jsi, found, copy); if (jperIsError(res)) @@ -867,8 +899,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiLast: { - JsonbValue tmpjbv; - JsonbValue *lastjbv; + JsonItem tmpjsi; + JsonItem *lastjsi; int last; bool hasNext = jspGetNext(jsp, &elem); @@ -883,15 +915,14 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, last = cxt->innermostArraySize - 1; - lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv)); + lastjsi = hasNext ? &tmpjsi : palloc(sizeof(*lastjsi)); - lastjbv->type = jbvNumeric; - lastjbv->val.numeric = + lastjsi->jbv.type = jbvNumeric; + lastjsi->jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(last))); - res = executeNextItem(cxt, jsp, &elem, - lastjbv, found, hasNext); + res = executeNextItem(cxt, jsp, &elem, lastjsi, found, hasNext); } break; @@ -905,7 +936,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeAnyItem (cxt, hasNext ? &elem : NULL, - jb->val.binary.data, found, 1, 1, 1, + jb->jbv.val.binary.data, found, 1, 1, 1, false, jspAutoUnwrap(cxt)); } else if (unwrap && JsonbType(jb) == jbvArray) @@ -986,7 +1017,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jb->type == jbvBinary) res = executeAnyItem (cxt, hasNext ? &elem : NULL, - jb->val.binary.data, found, + jb->jbv.val.binary.data, found, 1, jsp->content.anybounds.first, jsp->content.anybounds.last, @@ -1000,8 +1031,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiString: case jpiVariable: { - JsonbValue vbuf; - JsonbValue *v; + JsonItem vbuf; + JsonItem *v; bool hasNext = jspGetNext(jsp, &elem); if (!hasNext && !found) @@ -1023,14 +1054,13 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiType: { - JsonbValue *jbv = palloc(sizeof(*jbv)); + JsonItem jsi; - jbv->type = jbvString; - jbv->val.string.val = pstrdup(JsonbTypeName(jb)); - jbv->val.string.len = strlen(jbv->val.string.val); + jsi.jbv.type = jbvString; + jsi.jbv.val.string.val = pstrdup(JsonItemTypeName(jb)); + jsi.jbv.val.string.len = strlen(jsi.jbv.val.string.val); - res = executeNextItem(cxt, jsp, NULL, jbv, - found, false); + res = executeNextItem(cxt, jsp, NULL, &jsi, found, true); } break; @@ -1055,8 +1085,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = palloc(sizeof(*jb)); - jb->type = jbvNumeric; - jb->val.numeric = + jb->jbv.type = jbvNumeric; + jb->jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(size))); @@ -1078,7 +1108,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiDouble: { - JsonbValue jbv; + JsonItem jsi; if (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, @@ -1087,7 +1117,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jb->type == jbvNumeric) { char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, - NumericGetDatum(jb->val.numeric))); + NumericGetDatum(jb->jbv.val.numeric))); bool have_error = false; (void) float8in_internal_opt_error(tmp, @@ -1107,8 +1137,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { /* cast string as double */ double val; - char *tmp = pnstrdup(jb->val.string.val, - jb->val.string.len); + char *tmp = pnstrdup(jb->jbv.val.string.val, + jb->jbv.val.string.len); bool have_error = false; val = float8in_internal_opt_error(tmp, @@ -1123,10 +1153,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); - jb = &jbv; - jb->type = jbvNumeric; - jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric, - Float8GetDatum(val))); + jb = &jsi; + jb->jbv.type = jbvNumeric; + jb->jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric, + Float8GetDatum(val))); res = jperOk; } @@ -1161,8 +1191,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to a string value", jspOperationName(jsp->type))))); - datetime = cstring_to_text_with_len(jb->val.string.val, - jb->val.string.len); + datetime = cstring_to_text_with_len(jb->jbv.val.string.val, + jb->jbv.val.string.len); if (jsp->content.args.left) { @@ -1181,7 +1211,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { JsonValueList tzlist = {0}; JsonPathExecResult tzres; - JsonbValue *tzjbv; + JsonItem *tzjsi; jspGetRightArg(jsp, &elem); tzres = executeItem(cxt, &elem, jb, &tzlist); @@ -1189,21 +1219,21 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return tzres; if (JsonValueListLength(&tzlist) != 1 || - ((tzjbv = JsonValueListHead(&tzlist))->type != jbvString && - tzjbv->type != jbvNumeric)) + ((tzjsi = JsonValueListHead(&tzlist))->type != jbvString && + tzjsi->type != jbvNumeric)) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), errmsg("timezone argument of jsonpath item method .%s() is not a singleton string or number", jspOperationName(jsp->type))))); - if (tzjbv->type == jbvString) - tzname = pnstrdup(tzjbv->val.string.val, - tzjbv->val.string.len); + if (tzjsi->type == jbvString) + tzname = pnstrdup(tzjsi->jbv.val.string.val, + tzjsi->jbv.val.string.len); else { bool error = false; - tz = numeric_int4_opt_error(tzjbv->val.numeric, + tz = numeric_int4_opt_error(tzjsi->jbv.val.numeric, &error); if (error || tz == PG_INT32_MIN) @@ -1290,11 +1320,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); - jb->type = jbvDatetime; - jb->val.datetime.value = value; - jb->val.datetime.typid = typid; - jb->val.datetime.typmod = typmod; - jb->val.datetime.tz = tz; + jb->jbv.type = jbvDatetime; + jb->jbv.val.datetime.value = value; + jb->jbv.val.datetime.typid = typid; + jb->jbv.val.datetime.typmod = typmod; + jb->jbv.val.datetime.tz = tz; res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext); } @@ -1318,7 +1348,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found, + JsonItem *jb, JsonValueList *found, bool unwrapElements) { if (jb->type != jbvBinary) @@ -1328,7 +1358,7 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, } return executeAnyItem - (cxt, jsp, jb->val.binary.data, found, 1, 1, 1, + (cxt, jsp, jb->jbv.val.binary.data, found, 1, 1, 1, false, unwrapElements); } @@ -1339,7 +1369,7 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt, JsonPathItem *cur, JsonPathItem *next, - JsonbValue *v, JsonValueList *found, bool copy) + JsonItem *v, JsonValueList *found, bool copy) { JsonPathItem elem; bool hasNext; @@ -1358,7 +1388,7 @@ executeNextItem(JsonPathExecContext *cxt, return executeItem(cxt, next, v, found); if (found) - JsonValueListAppend(found, copy ? copyJsonbValue(v) : v); + JsonValueListAppend(found, copy ? copyJsonItem(v) : v); return jperOk; } @@ -1369,7 +1399,7 @@ executeNextItem(JsonPathExecContext *cxt, */ static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, bool unwrap, + JsonItem *jb, bool unwrap, JsonValueList *found) { if (unwrap && jspAutoUnwrap(cxt)) @@ -1377,7 +1407,7 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList seq = {0}; JsonValueListIterator it; JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq); - JsonbValue *item; + JsonItem *item; if (jperIsError(res)) return res; @@ -1405,7 +1435,7 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, bool unwrap, + JsonItem *jb, bool unwrap, JsonValueList *found) { JsonPathExecResult res; @@ -1421,7 +1451,7 @@ executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, /* Execute boolean-valued jsonpath expression. */ static JsonPathBool executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, bool canHaveNext) + JsonItem *jb, bool canHaveNext) { JsonPathItem larg; JsonPathItem rarg; @@ -1554,7 +1584,7 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb) + JsonItem *jb) { JsonItemStackEntry current; JsonPathBool res; @@ -1580,7 +1610,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, JsonPathExecResult res = jperNotFound; JsonbIterator *it; int32 r; - JsonbValue v; + JsonItem v; check_stack_depth(); @@ -1592,11 +1622,11 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, /* * Recursively iterate over jsonb objects/arrays */ - while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + while ((r = JsonbIteratorNext(&it, &v.jbv, true)) != WJB_DONE) { if (r == WJB_KEY) { - r = JsonbIteratorNext(&it, &v, true); + r = JsonbIteratorNext(&it, &v.jbv, true); Assert(r == WJB_VALUE); } @@ -1629,7 +1659,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, break; } else if (found) - JsonValueListAppend(found, copyJsonbValue(&v)); + JsonValueListAppend(found, copyJsonItem(&v)); else return jperOk; } @@ -1637,7 +1667,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, if (level < last && v.type == jbvBinary) { res = executeAnyItem - (cxt, jsp, v.val.binary.data, found, + (cxt, jsp, v.jbv.val.binary.data, found, level + 1, first, last, ignoreStructuralErrors, unwrapNext); @@ -1665,7 +1695,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, */ static JsonPathBool executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, - JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb, + JsonPathItem *larg, JsonPathItem *rarg, JsonItem *jb, bool unwrapRightArg, JsonPathPredicateCallback exec, void *param) { @@ -1673,7 +1703,7 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, JsonValueListIterator lseqit; JsonValueList lseq = {0}; JsonValueList rseq = {0}; - JsonbValue *lval; + JsonItem *lval; bool error = false; bool found = false; @@ -1695,7 +1725,7 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, while ((lval = JsonValueListNext(&lseq, &lseqit))) { JsonValueListIterator rseqit; - JsonbValue *rval; + JsonItem *rval; bool first = true; JsonValueListInitIterator(&rseq, &rseqit); @@ -1745,15 +1775,15 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, */ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, BinaryArithmFunc func, + JsonItem *jb, BinaryArithmFunc func, JsonValueList *found) { JsonPathExecResult jper; JsonPathItem elem; JsonValueList lseq = {0}; JsonValueList rseq = {0}; - JsonbValue *lval; - JsonbValue *rval; + JsonItem *lval; + JsonItem *rval; Numeric res; jspGetLeftArg(jsp, &elem); @@ -1788,13 +1818,13 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jspThrowErrors(cxt)) { - res = func(lval->val.numeric, rval->val.numeric, NULL); + res = func(lval->jbv.val.numeric, rval->jbv.val.numeric, NULL); } else { bool error = false; - res = func(lval->val.numeric, rval->val.numeric, &error); + res = func(lval->jbv.val.numeric, rval->jbv.val.numeric, &error); if (error) return jperError; @@ -1804,8 +1834,8 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperOk; lval = palloc(sizeof(*lval)); - lval->type = jbvNumeric; - lval->val.numeric = res; + lval->jbv.type = jbvNumeric; + lval->jbv.val.numeric = res; return executeNextItem(cxt, jsp, &elem, lval, found, false); } @@ -1816,14 +1846,14 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, PGFunction func, JsonValueList *found) + JsonItem *jb, PGFunction func, JsonValueList *found) { JsonPathExecResult jper; JsonPathExecResult jper2; JsonPathItem elem; JsonValueList seq = {0}; JsonValueListIterator it; - JsonbValue *val; + JsonItem *val; bool hasNext; jspGetArg(jsp, &elem); @@ -1856,9 +1886,9 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, } if (func) - val->val.numeric = + val->jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(func, - NumericGetDatum(val->val.numeric))); + NumericGetDatum(val->jbv.val.numeric))); jper2 = executeNextItem(cxt, jsp, &elem, val, found, false); @@ -1882,7 +1912,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, * Check if the 'whole' string starts from 'initial' string. */ static JsonPathBool -executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial, +executeStartsWith(JsonPathItem *jsp, JsonItem *whole, JsonItem *initial, void *param) { if (!(whole = getScalar(whole, jbvString))) @@ -1891,10 +1921,10 @@ executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial, if (!(initial = getScalar(initial, jbvString))) return jpbUnknown; /* error */ - if (whole->val.string.len >= initial->val.string.len && - !memcmp(whole->val.string.val, - initial->val.string.val, - initial->val.string.len)) + if (whole->jbv.val.string.len >= initial->jbv.val.string.len && + !memcmp(whole->jbv.val.string.val, + initial->jbv.val.string.val, + initial->jbv.val.string.len)) return jpbTrue; return jpbFalse; @@ -1906,7 +1936,7 @@ executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial, * Check if the string matches regex pattern. */ static JsonPathBool -executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, +executeLikeRegex(JsonPathItem *jsp, JsonItem *str, JsonItem *rarg, void *param) { JsonLikeRegexContext *cxt = param; @@ -1942,8 +1972,8 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, } } - if (RE_compile_and_execute(cxt->regex, str->val.string.val, - str->val.string.len, + if (RE_compile_and_execute(cxt->regex, str->jbv.val.string.val, + str->jbv.val.string.len, cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL)) return jpbTrue; @@ -1956,7 +1986,7 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg, */ static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, bool unwrap, PGFunction func, + JsonItem *jb, bool unwrap, PGFunction func, JsonValueList *found) { JsonPathItem next; @@ -1971,14 +2001,14 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); - datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric)); + datum = DirectFunctionCall1(func, NumericGetDatum(jb->jbv.val.numeric)); if (!jspGetNext(jsp, &next) && !found) return jperOk; jb = palloc(sizeof(*jb)); - jb->type = jbvNumeric; - jb->val.numeric = DatumGetNumeric(datum); + jb->jbv.type = jbvNumeric; + jb->jbv.val.numeric = DatumGetNumeric(datum); return executeNextItem(cxt, jsp, &next, jb, found, false); } @@ -2008,7 +2038,7 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found) + JsonItem *jb, JsonValueList *found) { JsonPathExecResult res = jperNotFound; JsonPathItem next; @@ -2030,7 +2060,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to an object", jspOperationName(jsp->type))))); - jbc = jb->val.binary.data; + jbc = jb->jbv.val.binary.data; if (!JsonContainerSize(jbc)) return jperNotFound; /* no key-value pairs */ @@ -2063,7 +2093,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) { JsonBaseObjectInfo baseObject; - JsonbValue obj; + JsonItem obj; JsonbParseState *ps; JsonbValue *keyval; Jsonb *jsonb; @@ -2095,7 +2125,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, jsonb = JsonbValueToJsonb(keyval); - JsonbInitBinary(&obj, jsonb); + JsonbInitBinary(&obj.jbv, jsonb); baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++); @@ -2122,22 +2152,22 @@ appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueList *found, JsonPathBool res) { JsonPathItem next; - JsonbValue jbv; + JsonItem jsi; if (!jspGetNext(jsp, &next) && !found) return jperOk; /* found singleton boolean value */ if (res == jpbUnknown) { - jbv.type = jbvNull; + jsi.jbv.type = jbvNull; } else { - jbv.type = jbvBool; - jbv.val.boolean = res == jpbTrue; + jsi.jbv.type = jbvBool; + jsi.jbv.val.boolean = res == jpbTrue; } - return executeNextItem(cxt, jsp, &next, &jbv, found, true); + return executeNextItem(cxt, jsp, &next, &jsi, found, true); } /* @@ -2147,7 +2177,7 @@ appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, - JsonbValue *value) + JsonItem *value) { switch (item->type) { @@ -2156,16 +2186,16 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, break; case jpiBool: value->type = jbvBool; - value->val.boolean = jspGetBool(item); + value->jbv.val.boolean = jspGetBool(item); break; case jpiNumeric: value->type = jbvNumeric; - value->val.numeric = jspGetNumeric(item); + value->jbv.val.numeric = jspGetNumeric(item); break; case jpiString: value->type = jbvString; - value->val.string.val = jspGetString(item, - &value->val.string.len); + value->jbv.val.string.val = jspGetString(item, + &value->jbv.val.string.len); break; case jpiVariable: getJsonPathVariable(cxt, item, value); @@ -2180,11 +2210,11 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, */ static void getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, - JsonbValue *value) + JsonItem *value) { char *varName; int varNameLength; - JsonbValue baseObject; + JsonItem baseObject; int baseObjectId; Assert(variable->type == jpiVariable); @@ -2192,7 +2222,7 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, if (!cxt->vars || (baseObjectId = cxt->getVar(cxt->vars, varName, varNameLength, value, - &baseObject)) < 0) + &baseObject.jbv)) < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find jsonpath variable \"%s\"", @@ -2204,7 +2234,7 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, - JsonbValue *value, JsonbValue *baseObject) + JsonItem *value, JsonbValue *baseObject) { Jsonb *vars = varsJsonb; JsonbValue tmp; @@ -2232,7 +2262,7 @@ getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, if (!v) return -1; - *value = *v; + value->jbv = *v; pfree(v); JsonbInitBinary(baseObject, vars); @@ -2245,13 +2275,13 @@ getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, * Returns the size of an array item, or -1 if item is not an array. */ static int -JsonbArraySize(JsonbValue *jb) +JsonbArraySize(JsonItem *jb) { Assert(jb->type != jbvArray); if (jb->type == jbvBinary) { - JsonbContainer *jbc = jb->val.binary.data; + JsonbContainer *jbc = jb->jbv.val.binary.data; if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) return JsonContainerSize(jbc); @@ -2262,7 +2292,7 @@ JsonbArraySize(JsonbValue *jb) /* Comparison predicate callback. */ static JsonPathBool -executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p) +executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p) { return compareItems(cmp->type, lv, rv); } @@ -2271,8 +2301,10 @@ executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p) * Compare two SQL/JSON items using comparison operation 'op'. */ static JsonPathBool -compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2) +compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) { + JsonbValue *jb1 = &jsi1->jbv; + JsonbValue *jb2 = &jsi2->jbv; int cmp; bool res; @@ -2376,25 +2408,45 @@ compareNumeric(Numeric a, Numeric b) NumericGetDatum(b))); } -static JsonbValue * -copyJsonbValue(JsonbValue *src) +static JsonItem * +copyJsonItem(JsonItem *src) { - JsonbValue *dst = palloc(sizeof(*dst)); + JsonItem *dst = palloc(sizeof(*dst)); *dst = *src; return dst; } +static JsonbValue * +JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) +{ + return &jsi->jbv; +} + +static Jsonb * +JsonItemToJsonb(JsonItem *jsi) +{ + JsonbValue jbv; + + return JsonbValueToJsonb(JsonItemToJsonbValue(jsi, &jbv)); +} + +static const char * +JsonItemTypeName(JsonItem *jsi) +{ + return JsonbTypeName(&jsi->jbv); +} + /* * Execute array subscript expression and convert resulting numeric item to * the integer type with truncation. */ static JsonPathExecResult -getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, +getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, int32 *index) { - JsonbValue *jbv; + JsonItem *jbv; JsonValueList found = {0}; JsonPathExecResult res = executeItem(cxt, jsp, jb, &found); Datum numeric_index; @@ -2410,7 +2462,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, errmsg("jsonpath array subscript is not a single numeric value")))); numeric_index = DirectFunctionCall2(numeric_trunc, - NumericGetDatum(jbv->val.numeric), + NumericGetDatum(jbv->jbv.val.numeric), Int32GetDatum(0)); *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index), @@ -2426,19 +2478,19 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, /* Save base object and its id needed for the execution of .keyvalue(). */ static JsonBaseObjectInfo -setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id) +setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id) { JsonBaseObjectInfo baseObject = cxt->baseObject; cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL : - (JsonbContainer *) jbv->val.binary.data; + (JsonbContainer *) jbv->jbv.val.binary.data; cxt->baseObject.id = id; return baseObject; } static void -JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv) +JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv) { if (jvl->singleton) { @@ -2463,7 +2515,7 @@ JsonValueListIsEmpty(JsonValueList *jvl) return !jvl->singleton && list_length(jvl->list) <= 0; } -static JsonbValue * +static JsonItem * JsonValueListHead(JsonValueList *jvl) { return jvl->singleton ? jvl->singleton : linitial(jvl->list); @@ -2488,7 +2540,7 @@ JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) } else if (list_head(jvl->list) != NULL) { - it->value = (JsonbValue *) linitial(jvl->list); + it->value = (JsonItem *) linitial(jvl->list); it->next = lnext(list_head(jvl->list)); } else @@ -2501,10 +2553,10 @@ JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) /* * Get the next item from the sequence advancing iterator. */ -static JsonbValue * +static JsonItem * JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) { - JsonbValue *result = it->value; + JsonItem *result = it->value; if (it->next) { @@ -2536,13 +2588,13 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is. */ static int -JsonbType(JsonbValue *jb) +JsonbType(JsonItem *jb) { int type = jb->type; if (jb->type == jbvBinary) { - JsonbContainer *jbc = (void *) jb->val.binary.data; + JsonbContainer *jbc = (void *) jb->jbv.val.binary.data; /* Scalars should be always extracted during jsonpath execution. */ Assert(!JsonContainerIsScalar(jbc)); @@ -2620,13 +2672,19 @@ JsonbValueUnquoteText(JsonbValue *jbv) return cstring_to_text_with_len(str, len); } +static text * +JsonItemUnquoteText(JsonItem *jsi) +{ + return JsonbValueUnquoteText(&jsi->jbv); +} + /* Get scalar of given type or NULL on type mismatch */ -static JsonbValue * -getScalar(JsonbValue *scalar, enum jbvType type) +static JsonItem * +getScalar(JsonItem *scalar, enum jbvType type) { /* Scalars should be always extracted during jsonpath execution. */ Assert(scalar->type != jbvBinary || - !JsonContainerIsScalar(scalar->val.binary.data)); + !JsonContainerIsScalar(scalar->jbv.val.binary.data)); return scalar->type == type ? scalar : NULL; } @@ -2637,20 +2695,23 @@ wrapItemsInArray(const JsonValueList *items) { JsonbParseState *ps = NULL; JsonValueListIterator it; - JsonbValue *jbv; + JsonItem *jsi; pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); JsonValueListInitIterator(items, &it); - while ((jbv = JsonValueListNext(items, &it))) - pushJsonbValue(&ps, WJB_ELEM, jbv); + while ((jsi = JsonValueListNext(items, &it))) + { + JsonbValue jbv; + + pushJsonbValue(&ps, WJB_ELEM, JsonItemToJsonbValue(jsi, &jbv)); + } return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } static void -pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, - JsonbValue *item) +pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item) { entry->item = item; entry->parent = *stack; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 32af37002e..41bcb6090a 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1121,6 +1121,7 @@ JsValue JsonAggState JsonBaseObjectInfo JsonHashEntry +JsonItem JsonItemStack JsonItemStackEntry JsonIterateStringValuesAction From 9e9a26deb202cc8686e90ac35ddda4956ecc3bd5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 31 Jan 2019 20:28:42 +0300 Subject: [PATCH 12/66] Move JsonbValue.val.datetime to JsonItem.datetime --- src/backend/utils/adt/jsonb.c | 18 ---- src/backend/utils/adt/jsonb_util.c | 17 ---- src/backend/utils/adt/jsonpath_exec.c | 139 +++++++++++++++++++------- src/include/utils/jsonb.h | 19 +--- 4 files changed, 106 insertions(+), 87 deletions(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 74b4bbe44c..dc00a7c8b4 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -206,24 +206,6 @@ JsonbTypeName(JsonbValue *jbv) return "boolean"; case jbvNull: return "null"; - case jbvDatetime: - switch (jbv->val.datetime.typid) - { - case DATEOID: - return "date"; - case TIMEOID: - return "time without time zone"; - case TIMETZOID: - return "time with time zone"; - case TIMESTAMPOID: - return "timestamp without time zone"; - case TIMESTAMPTZOID: - return "timestamp with time zone"; - default: - elog(ERROR, "unrecognized jsonb value datetime type: %d", - jbv->val.datetime.typid); - } - return "unknown"; default: elog(ERROR, "unrecognized jsonb value type: %d", jbv->type); return "unknown"; diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index cb2bd872cf..966e0fc0a7 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -244,7 +244,6 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) res = (va.val.object.nPairs > vb.val.object.nPairs) ? 1 : -1; break; case jbvBinary: - case jbvDatetime: elog(ERROR, "unexpected jbvBinary value"); } } @@ -1753,22 +1752,6 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE; break; - case jbvDatetime: - { - char buf[MAXDATELEN + 1]; - size_t len; - - JsonEncodeDateTime(buf, - scalarVal->val.datetime.value, - scalarVal->val.datetime.typid, - &scalarVal->val.datetime.tz); - len = strlen(buf); - appendToBuffer(buffer, buf, len); - - *jentry = JENTRY_ISSTRING | len; - } - break; - default: elog(ERROR, "invalid jsonb scalar type"); } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 829ec2c32f..f52530b432 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -79,11 +79,43 @@ #include "utils/varlena.h" +typedef enum JsonItemType +{ + /* Scalar types */ + jsiNull = jbvNull, + jsiString = jbvString, + jsiNumeric = jbvNumeric, + jsiBool = jbvBool, + /* Composite types */ + jsiArray = jbvArray, + jsiObject = jbvObject, + /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ + jsiBinary = jbvBinary, + + /* + * Virtual types. + * + * These types are used only for in-memory JSON processing and serialized + * into JSON strings when outputted to json/jsonb. + */ + jsiDatetime = 0x20 +} JsonItemType; + /* SQL/JSON item */ typedef union JsonItem { - JsonbValue jbv; - enum jbvType type; + int type; /* XXX JsonItemType */ + + JsonbValue jbv; + + struct + { + enum jbvType type; + Datum value; + Oid typid; + int32 typmod; + int tz; + } datetime; } JsonItem; #define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) @@ -318,8 +350,7 @@ static int JsonbType(JsonItem *jb); static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items); -static text *JsonbValueUnquoteText(JsonbValue *jbv); -static text *JsonItemUnquoteText(JsonItem *jbv); +static text *JsonItemUnquoteText(JsonItem *jsi); static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, @@ -1320,11 +1351,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); - jb->jbv.type = jbvDatetime; - jb->jbv.val.datetime.value = value; - jb->jbv.val.datetime.typid = typid; - jb->jbv.val.datetime.typmod = typmod; - jb->jbv.val.datetime.tz = tz; + jb->type = jsiDatetime; + jb->datetime.value = value; + jb->datetime.typid = typid; + jb->datetime.typmod = typmod; + jb->datetime.tz = tz; res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext); } @@ -2322,7 +2353,7 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) return jpbUnknown; } - switch (jb1->type) + switch (jsi1->type) { case jbvNull: cmp = 0; @@ -2345,16 +2376,16 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) jb2->val.string.val, jb2->val.string.len, DEFAULT_COLLATION_OID); break; - case jbvDatetime: + case jsiDatetime: { bool error = false; - cmp = compareDatetime(jb1->val.datetime.value, - jb1->val.datetime.typid, - jb1->val.datetime.tz, - jb2->val.datetime.value, - jb2->val.datetime.typid, - jb2->val.datetime.tz, + cmp = compareDatetime(jsi1->datetime.value, + jsi1->datetime.typid, + jsi1->datetime.tz, + jsi2->datetime.value, + jsi2->datetime.typid, + jsi2->datetime.tz, &error); if (error) @@ -2421,7 +2452,20 @@ copyJsonItem(JsonItem *src) static JsonbValue * JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) { - return &jsi->jbv; + switch (jsi->type) + { + case jsiDatetime: + jbv->type = jbvString; + jbv->val.string.val = JsonEncodeDateTime(NULL, + jsi->datetime.value, + jsi->datetime.typid, + &jsi->datetime.tz); + jbv->val.string.len = strlen(jbv->val.string.val); + return jbv; + + default: + return &jsi->jbv; + } } static Jsonb * @@ -2435,7 +2479,30 @@ JsonItemToJsonb(JsonItem *jsi) static const char * JsonItemTypeName(JsonItem *jsi) { - return JsonbTypeName(&jsi->jbv); + switch (jsi->type) + { + case jsiDatetime: + switch (jsi->datetime.typid) + { + case DATEOID: + return "date"; + case TIMEOID: + return "time without time zone"; + case TIMETZOID: + return "time with time zone"; + case TIMESTAMPOID: + return "timestamp without time zone"; + case TIMESTAMPTZOID: + return "timestamp with time zone"; + default: + elog(ERROR, "unrecognized jsonb value datetime type: %d", + jsi->datetime.typid); + return "unknown"; + } + + default: + return JsonbTypeName(&jsi->jbv); + } } /* @@ -2635,13 +2702,6 @@ JsonbValueUnquote(JsonbValue *jbv, int *len) *len = 4; return "null"; - case jbvDatetime: - *len = -1; - return JsonEncodeDateTime(NULL, - jbv->val.datetime.value, - jbv->val.datetime.typid, - &jbv->val.datetime.tz); - case jbvBinary: { JsonbValue jbvbuf; @@ -2660,11 +2720,28 @@ JsonbValueUnquote(JsonbValue *jbv, int *len) } } +static char * +JsonItemUnquote(JsonItem *jsi, int *len) +{ + switch (jsi->type) + { + case jsiDatetime: + *len = -1; + return JsonEncodeDateTime(NULL, + jsi->datetime.value, + jsi->datetime.typid, + &jsi->datetime.tz); + + default: + return JsonbValueUnquote(&jsi->jbv, len); + } +} + static text * -JsonbValueUnquoteText(JsonbValue *jbv) +JsonItemUnquoteText(JsonItem *jsi) { int len; - char *str = JsonbValueUnquote(jbv, &len); + char *str = JsonItemUnquote(jsi, &len); if (len < 0) return cstring_to_text(str); @@ -2672,12 +2749,6 @@ JsonbValueUnquoteText(JsonbValue *jbv) return cstring_to_text_with_len(str, len); } -static text * -JsonItemUnquoteText(JsonItem *jsi) -{ - return JsonbValueUnquoteText(&jsi->jbv); -} - /* Get scalar of given type or NULL on type mismatch */ static JsonItem * getScalar(JsonItem *scalar, enum jbvType type) diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 6a7a37bad8..94e12796b7 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -242,14 +242,6 @@ enum jbvType jbvObject, /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ jbvBinary, - - /* - * Virtual types. - * - * These types are used only for in-memory JSON processing and serialized - * into JSON strings when outputted to json/jsonb. - */ - jbvDatetime = 0x20, }; /* @@ -290,20 +282,11 @@ struct JsonbValue int len; JsonbContainer *data; } binary; /* Array or object, in on-disk format */ - - struct - { - Datum value; - Oid typid; - int32 typmod; - int tz; - } datetime; } val; }; #define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \ - (jsonbval)->type <= jbvBool) || \ - (jsonbval)->type == jbvDatetime) + (jsonbval)->type <= jbvBool)) /* * Key/value pair within an Object. From 0865bae13da7f5c2b498f919082fdf4aa74b4738 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 23:48:33 +0300 Subject: [PATCH 13/66] Extract getJsonObjectKey(), getJsonArrayElement() --- src/backend/utils/adt/jsonpath_exec.c | 57 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index f52530b432..e0220e12c1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -167,6 +167,7 @@ typedef struct JsonPathExecContext * ignored */ bool throwErrors; /* with "false" all suppressible errors are * suppressed */ + bool isJsonb; } JsonPathExecContext; /* Context for LIKE_REGEX execution. */ @@ -352,6 +353,10 @@ static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items); static text *JsonItemUnquoteText(JsonItem *jsi); +static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, + bool isJsonb); +static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index); + static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, int32 *typmod, int *tzp, bool throwErrors); @@ -671,6 +676,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, cxt.lastGeneratedObjectId = 1 + getVar(vars, NULL, 0, NULL, NULL); cxt.innermostArraySize = -1; cxt.throwErrors = throwErrors; + cxt.isJsonb = true; pushJsonItem(&cxt.stack, &root, cxt.root); @@ -749,23 +755,18 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiKey: if (JsonbType(jb) == jbvObject) { - JsonbValue *v; - JsonbValue key; - - key.type = jbvString; - key.val.string.val = jspGetString(jsp, &key.val.string.len); + int keylen; + char *key = jspGetString(jsp, &keylen); - v = findJsonbValueFromContainer(jb->jbv.val.binary.data, - JB_FOBJECT, &key); + jb = getJsonObjectKey(jb, key, keylen, cxt->isJsonb); - if (v != NULL) + if (jb != NULL) { - jb = JsonbValueToJsonItem(v); res = executeNextItem(cxt, jsp, NULL, jb, found, false); /* free value if it was not added to found list */ if (jspHasNext(jsp) || !found) - pfree(v); + pfree(jb); } else if (!jspIgnoreStructuralErrors(cxt)) { @@ -777,8 +778,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, ereport(ERROR, (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), errmsg("JSON object does not contain key \"%s\"", - pnstrdup(key.val.string.val, - key.val.string.len)))); + pnstrdup(key, keylen)))); } } else if (unwrap && JsonbType(jb) == jbvArray) @@ -887,14 +887,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } else { - JsonbValue *v = - getIthJsonbValueFromContainer(jb->jbv.val.binary.data, - (uint32) index); + jsi = getJsonArrayElement(jb, (uint32) index); - if (v == NULL) + if (jsi == NULL) continue; - jsi = JsonbValueToJsonItem(v); copy = false; } @@ -2749,6 +2746,32 @@ JsonItemUnquoteText(JsonItem *jsi) return cstring_to_text_with_len(str, len); } +static JsonItem * +getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb) +{ + JsonbValue *val; + JsonbValue key; + + key.type = jbvString; + key.val.string.val = keystr; + key.val.string.len = keylen; + + val = isJsonb ? + findJsonbValueFromContainer(jb->jbv.val.binary.data, JB_FOBJECT, &key) : + getIthJsonbValueFromContainer(jb->jbv.val.binary.data, 0); + + return val ? JsonbValueToJsonItem(val) : NULL; +} + +static JsonItem * +getJsonArrayElement(JsonItem *jb, uint32 index) +{ + JsonbValue *elem = + getIthJsonbValueFromContainer(jb->jbv.val.binary.data, index); + + return elem ? JsonbValueToJsonItem(elem) : NULL; +} + /* Get scalar of given type or NULL on type mismatch */ static JsonItem * getScalar(JsonItem *scalar, enum jbvType type) From c4939375adee345f615fd33b14481e2c96f40bbe Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 26 Feb 2019 23:49:08 +0300 Subject: [PATCH 14/66] Optimize copying of singleton item into empty list --- src/backend/utils/adt/jsonpath_exec.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index e0220e12c1..b47384f391 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1068,7 +1068,6 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, res = jperOk; /* skip evaluation */ break; } - v = hasNext ? &vbuf : palloc(sizeof(*v)); baseObject = cxt->baseObject; @@ -1436,10 +1435,28 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueListIterator it; JsonPathExecResult res = executeItem(cxt, jsp, jb, &seq); JsonItem *item; + int count; if (jperIsError(res)) return res; + count = JsonValueListLength(&seq); + + if (!count) + return jperNotFound; + + /* Optimize copying of singleton item into empty list */ + if (count == 1 && + JsonbType((item = JsonValueListHead(&seq))) != jbvArray) + { + if (JsonValueListIsEmpty(found)) + *found = seq; + else + JsonValueListAppend(found, item); + + return jperOk; + } + JsonValueListInitIterator(&seq, &it); while ((item = JsonValueListNext(&seq, &it))) { From a1b18b1b9118769f15ded893f5fcf81e1fa4aa2e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 15:54:51 +0300 Subject: [PATCH 15/66] Add jsonpath support for json type --- src/backend/catalog/system_views.sql | 50 + src/backend/utils/adt/Makefile | 4 +- src/backend/utils/adt/json.c | 855 +++++++- src/backend/utils/adt/jsonb_util.c | 19 +- src/backend/utils/adt/jsonpath_exec.c | 14 +- src/backend/utils/adt/jsonpath_json.c | 26 + src/include/catalog/pg_operator.dat | 8 + src/include/catalog/pg_proc.dat | 30 + src/include/utils/jsonapi.h | 61 + src/include/utils/jsonb.h | 15 +- src/include/utils/jsonpath_json.h | 106 + src/test/regress/expected/json_jsonpath.out | 2097 +++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/json_jsonpath.sql | 477 +++++ 15 files changed, 3733 insertions(+), 32 deletions(-) create mode 100644 src/backend/utils/adt/jsonpath_json.c create mode 100644 src/include/utils/jsonpath_json.h create mode 100644 src/test/regress/expected/json_jsonpath.out create mode 100644 src/test/regress/sql/json_jsonpath.sql diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 6af68d0d72..54da52596d 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1295,6 +1295,56 @@ LANGUAGE INTERNAL STRICT IMMUTABLE PARALLEL SAFE AS 'jsonb_path_query_first_text'; + + +CREATE OR REPLACE FUNCTION + json_path_exists(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS boolean +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_exists'; + +CREATE OR REPLACE FUNCTION + json_path_match(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS boolean +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_match'; + +CREATE OR REPLACE FUNCTION + json_path_query(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS SETOF json +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_query'; + +CREATE OR REPLACE FUNCTION + json_path_query_array(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS json +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_query_array'; + +CREATE OR REPLACE FUNCTION + json_path_query_first(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS json +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_query_first'; + +CREATE OR REPLACE FUNCTION + json_path_query_first_text(target json, path jsonpath, vars json DEFAULT '{}', + silent boolean DEFAULT false) +RETURNS text +LANGUAGE INTERNAL +STRICT IMMUTABLE PARALLEL SAFE +AS 'json_path_query_first_text'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 580043233b..8d81cc0d8b 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,7 +17,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ float.o format_type.o formatting.o genfile.o \ geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \ int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \ - jsonfuncs.o jsonpath_gram.o jsonpath.o jsonpath_exec.o \ + jsonfuncs.o jsonpath_gram.o jsonpath.o jsonpath_exec.o jsonpath_json.o \ like.o like_support.o lockfuncs.o mac.o mac8.o misc.o name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ @@ -39,6 +39,8 @@ jsonpath_scan.c: FLEX_NO_BACKUP=yes # jsonpath_scan is compiled as part of jsonpath_gram jsonpath_gram.o: jsonpath_scan.c +jsonpath_json.o: jsonpath_exec.c + # jsonpath_gram.c and jsonpath_scan.c are in the distribution tarball, # so they are not cleaned here. clean distclean maintainer-clean: diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 7440f77cbe..fddcf443b1 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -106,6 +106,9 @@ static void add_json(Datum val, bool is_null, StringInfo result, Oid val_type, bool key_scalar); static text *catenate_stringinfo_string(StringInfo buffer, const char *addon); +static JsonIterator *JsonIteratorInitFromLex(JsonContainer *jc, + JsonLexContext *lex, JsonIterator *parent); + /* the null action object used for pure validation */ static JsonSemAction nullSemAction = { @@ -126,6 +129,27 @@ lex_peek(JsonLexContext *lex) return lex->token_type; } +/* + * lex_peek_value + * + * get the current look_ahead de-escaped lexeme. +*/ +static inline char * +lex_peek_value(JsonLexContext *lex) +{ + if (lex->token_type == JSON_TOKEN_STRING) + return lex->strval ? pstrdup(lex->strval->data) : NULL; + else + { + int len = lex->token_terminator - lex->token_start; + char *tokstr = palloc(len + 1); + + memcpy(tokstr, lex->token_start, len); + tokstr[len] = '\0'; + return tokstr; + } +} + /* * lex_accept * @@ -141,22 +165,8 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme) if (lex->token_type == token) { if (lexeme != NULL) - { - if (lex->token_type == JSON_TOKEN_STRING) - { - if (lex->strval != NULL) - *lexeme = pstrdup(lex->strval->data); - } - else - { - int len = (lex->token_terminator - lex->token_start); - char *tokstr = palloc(len + 1); + *lexeme = lex_peek_value(lex); - memcpy(tokstr, lex->token_start, len); - tokstr[len] = '\0'; - *lexeme = tokstr; - } - } json_lex(lex); return true; } @@ -2575,3 +2585,818 @@ json_typeof(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(type)); } + +/* + * Initialize a JsonContainer from a json text, its type and size. + * 'type' can be JB_FOBJECT, JB_FARRAY, (JB_FARRAY | JB_FSCALAR). + * 'size' is a number of elements/pairs in array/object, or -1 if unknown. + */ +static void +jsonInitContainer(JsonContainerData *jc, char *json, int len, int type, + int size) +{ + if (size < 0 || size > JB_CMASK) + size = JB_CMASK; /* unknown size */ + + jc->data = json; + jc->len = len; + jc->header = type | size; +} + +/* + * Initialize a JsonContainer from a text datum. + */ +static void +jsonInit(JsonContainerData *jc, Datum value) +{ + text *json = DatumGetTextP(value); + JsonLexContext *lex = makeJsonLexContext(json, false); + JsonTokenType tok; + int type; + int size = -1; + + /* Lex exactly one token from the input and check its type. */ + json_lex(lex); + tok = lex_peek(lex); + + switch (tok) + { + case JSON_TOKEN_OBJECT_START: + type = JB_FOBJECT; + lex_accept(lex, tok, NULL); + if (lex_peek(lex) == JSON_TOKEN_OBJECT_END) + size = 0; + break; + case JSON_TOKEN_ARRAY_START: + type = JB_FARRAY; + lex_accept(lex, tok, NULL); + if (lex_peek(lex) == JSON_TOKEN_ARRAY_END) + size = 0; + break; + case JSON_TOKEN_STRING: + case JSON_TOKEN_NUMBER: + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + case JSON_TOKEN_NULL: + type = JB_FARRAY | JB_FSCALAR; + size = 1; + break; + default: + elog(ERROR, "unexpected json token: %d", tok); + type = jbvNull; + break; + } + + pfree(lex); + + jsonInitContainer(jc, VARDATA(json), VARSIZE(json) - VARHDRSZ, type, size); +} + +/* + * Wrap JSON text into a palloc()'d Json structure. + */ +Json * +JsonCreate(text *json) +{ + Json *res = palloc0(sizeof(*res)); + + jsonInit((JsonContainerData *) &res->root, PointerGetDatum(json)); + + return res; +} + +/* + * Fill JsonbValue from the current iterator token. + * Returns true if recursion into nested object or array is needed (in this case + * child iterator is created and put into *pit). + */ +static bool +jsonFillValue(JsonIterator **pit, JsonbValue *res, bool skipNested, + JsontIterState nextState) +{ + JsonIterator *it = *pit; + JsonLexContext *lex = it->lex; + JsonTokenType tok = lex_peek(lex); + + switch (tok) + { + case JSON_TOKEN_NULL: + res->type = jbvNull; + break; + + case JSON_TOKEN_TRUE: + res->type = jbvBool; + res->val.boolean = true; + break; + + case JSON_TOKEN_FALSE: + res->type = jbvBool; + res->val.boolean = false; + break; + + case JSON_TOKEN_STRING: + { + char *token = lex_peek_value(lex); + res->type = jbvString; + res->val.string.val = token; + res->val.string.len = strlen(token); + break; + } + + case JSON_TOKEN_NUMBER: + { + char *token = lex_peek_value(lex); + res->type = jbvNumeric; + res->val.numeric = DatumGetNumeric(DirectFunctionCall3( + numeric_in, CStringGetDatum(token), 0, -1)); + break; + } + + case JSON_TOKEN_OBJECT_START: + case JSON_TOKEN_ARRAY_START: + { + JsonContainerData *cont = palloc(sizeof(*cont)); + char *token_start = lex->token_start; + int len; + + if (skipNested) + { + /* find the end of a container for its length calculation */ + if (tok == JSON_TOKEN_OBJECT_START) + parse_object(lex, &nullSemAction); + else + parse_array(lex, &nullSemAction); + + len = lex->token_start - token_start; + } + else + len = lex->input_length - (lex->token_start - lex->input); + + jsonInitContainer(cont, + token_start, len, + tok == JSON_TOKEN_OBJECT_START ? + JB_FOBJECT : JB_FARRAY, + -1); + + res->type = jbvBinary; + res->val.binary.data = (JsonbContainer *) cont; + res->val.binary.len = len; + + if (skipNested) + return false; + + /* recurse into container */ + it->state = nextState; + *pit = JsonIteratorInitFromLex(cont, lex, *pit); + return true; + } + + default: + report_parse_error(JSON_PARSE_VALUE, lex); + } + + lex_accept(lex, tok, NULL); + + return false; +} + +/* + * Free the topmost entry in the stack of JsonIterators. + */ +static inline JsonIterator * +JsonIteratorFreeAndGetParent(JsonIterator *it) +{ + JsonIterator *parent = it->parent; + + pfree(it); + + return parent; +} + +/* + * Free the entire stack of JsonIterators. + */ +void +JsonIteratorFree(JsonIterator *it) +{ + while (it) + it = JsonIteratorFreeAndGetParent(it); +} + +/* + * Get next JsonbValue while iterating through JsonContainer. + * + * For more details, see JsonbIteratorNext(). + */ +JsonbIteratorToken +JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested) +{ + JsonIterator *it; + + if (*pit == NULL) + return WJB_DONE; + +recurse: + it = *pit; + + /* parse by recursive descent */ + switch (it->state) + { + case JTI_ARRAY_START: + val->type = jbvArray; + val->val.array.nElems = it->isScalar ? 1 : -1; + val->val.array.rawScalar = it->isScalar; + val->val.array.elems = NULL; + it->state = it->isScalar ? JTI_ARRAY_ELEM_SCALAR : JTI_ARRAY_ELEM; + return WJB_BEGIN_ARRAY; + + case JTI_ARRAY_ELEM_SCALAR: + { + (void) jsonFillValue(pit, val, skipNested, JTI_ARRAY_END); + it->state = JTI_ARRAY_END; + return WJB_ELEM; + } + + case JTI_ARRAY_END: + if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END) + report_parse_error(JSON_PARSE_END, it->lex); + *pit = JsonIteratorFreeAndGetParent(*pit); + return WJB_END_ARRAY; + + case JTI_ARRAY_ELEM: + if (lex_accept(it->lex, JSON_TOKEN_ARRAY_END, NULL)) + { + it->state = JTI_ARRAY_END; + goto recurse; + } + + if (jsonFillValue(pit, val, skipNested, JTI_ARRAY_ELEM_AFTER)) + goto recurse; + + /* fall through */ + + case JTI_ARRAY_ELEM_AFTER: + if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL)) + { + if (lex_peek(it->lex) != JSON_TOKEN_ARRAY_END) + report_parse_error(JSON_PARSE_ARRAY_NEXT, it->lex); + } + + if (it->state == JTI_ARRAY_ELEM_AFTER) + { + it->state = JTI_ARRAY_ELEM; + goto recurse; + } + + return WJB_ELEM; + + case JTI_OBJECT_START: + val->type = jbvObject; + val->val.object.nPairs = -1; + val->val.object.pairs = NULL; + val->val.object.uniquify = false; + it->state = JTI_OBJECT_KEY; + return WJB_BEGIN_OBJECT; + + case JTI_OBJECT_KEY: + if (lex_accept(it->lex, JSON_TOKEN_OBJECT_END, NULL)) + { + if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END) + report_parse_error(JSON_PARSE_END, it->lex); + *pit = JsonIteratorFreeAndGetParent(*pit); + return WJB_END_OBJECT; + } + + if (lex_peek(it->lex) != JSON_TOKEN_STRING) + report_parse_error(JSON_PARSE_OBJECT_START, it->lex); + + (void) jsonFillValue(pit, val, true, JTI_OBJECT_VALUE); + + if (!lex_accept(it->lex, JSON_TOKEN_COLON, NULL)) + report_parse_error(JSON_PARSE_OBJECT_LABEL, it->lex); + + it->state = JTI_OBJECT_VALUE; + return WJB_KEY; + + case JTI_OBJECT_VALUE: + if (jsonFillValue(pit, val, skipNested, JTI_OBJECT_VALUE_AFTER)) + goto recurse; + + /* fall through */ + + case JTI_OBJECT_VALUE_AFTER: + if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL)) + { + if (lex_peek(it->lex) != JSON_TOKEN_OBJECT_END) + report_parse_error(JSON_PARSE_OBJECT_NEXT, it->lex); + } + + if (it->state == JTI_OBJECT_VALUE_AFTER) + { + it->state = JTI_OBJECT_KEY; + goto recurse; + } + + it->state = JTI_OBJECT_KEY; + return WJB_VALUE; + + default: + break; + } + + return WJB_DONE; +} + +/* Initialize JsonIterator from json lexer which onto the first token. */ +static JsonIterator * +JsonIteratorInitFromLex(JsonContainer *jc, JsonLexContext *lex, + JsonIterator *parent) +{ + JsonIterator *it = palloc(sizeof(JsonIterator)); + JsonTokenType tok; + + it->container = jc; + it->parent = parent; + it->lex = lex; + + tok = lex_peek(it->lex); + + switch (tok) + { + case JSON_TOKEN_OBJECT_START: + it->isScalar = false; + it->state = JTI_OBJECT_START; + lex_accept(it->lex, tok, NULL); + break; + case JSON_TOKEN_ARRAY_START: + it->isScalar = false; + it->state = JTI_ARRAY_START; + lex_accept(it->lex, tok, NULL); + break; + case JSON_TOKEN_STRING: + case JSON_TOKEN_NUMBER: + case JSON_TOKEN_TRUE: + case JSON_TOKEN_FALSE: + case JSON_TOKEN_NULL: + it->isScalar = true; + it->state = JTI_ARRAY_START; + break; + default: + report_parse_error(JSON_PARSE_VALUE, it->lex); + } + + return it; +} + +/* + * Given a JsonContainer, expand to JsonIterator to iterate over items + * fully expanded to in-memory representation for manipulation. + * + * See JsonbIteratorNext() for notes on memory management. + */ +JsonIterator * +JsonIteratorInit(JsonContainer *jc) +{ + JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, true); + json_lex(lex); + return JsonIteratorInitFromLex(jc, lex, NULL); +} + +/* + * Serialize a single JsonbValue into text buffer. + */ +static void +JsonEncodeJsonbValue(StringInfo buf, JsonbValue *jbv) +{ + check_stack_depth(); + + switch (jbv->type) + { + case jbvNull: + appendBinaryStringInfo(buf, "null", 4); + break; + + case jbvBool: + if (jbv->val.boolean) + appendBinaryStringInfo(buf, "true", 4); + else + appendBinaryStringInfo(buf, "false", 5); + break; + + case jbvNumeric: + /* replace numeric NaN with string "NaN" */ + if (numeric_is_nan(jbv->val.numeric)) + appendBinaryStringInfo(buf, "\"NaN\"", 5); + else + { + Datum str = DirectFunctionCall1(numeric_out, + NumericGetDatum(jbv->val.numeric)); + + appendStringInfoString(buf, DatumGetCString(str)); + } + break; + + case jbvString: + { + char *str = jbv->val.string.len < 0 ? jbv->val.string.val : + pnstrdup(jbv->val.string.val, jbv->val.string.len); + + escape_json(buf, str); + + if (jbv->val.string.len >= 0) + pfree(str); + + break; + } + + case jbvArray: + { + int i; + + if (!jbv->val.array.rawScalar) + appendStringInfoChar(buf, '['); + + for (i = 0; i < jbv->val.array.nElems; i++) + { + if (i > 0) + appendBinaryStringInfo(buf, ", ", 2); + + JsonEncodeJsonbValue(buf, &jbv->val.array.elems[i]); + } + + if (!jbv->val.array.rawScalar) + appendStringInfoChar(buf, ']'); + + break; + } + + case jbvObject: + { + int i; + + appendStringInfoChar(buf, '{'); + + for (i = 0; i < jbv->val.object.nPairs; i++) + { + if (i > 0) + appendBinaryStringInfo(buf, ", ", 2); + + JsonEncodeJsonbValue(buf, &jbv->val.object.pairs[i].key); + appendBinaryStringInfo(buf, ": ", 2); + JsonEncodeJsonbValue(buf, &jbv->val.object.pairs[i].value); + } + + appendStringInfoChar(buf, '}'); + break; + } + + case jbvBinary: + { + JsonContainer *json = (JsonContainer *) jbv->val.binary.data; + + appendBinaryStringInfo(buf, json->data, json->len); + break; + } + + default: + elog(ERROR, "unknown jsonb value type: %d", jbv->type); + break; + } +} + +/* + * Turn an in-memory JsonbValue into a json for on-disk storage. + */ +Json * +JsonbValueToJson(JsonbValue *jbv) +{ + StringInfoData buf; + Json *json = palloc0(sizeof(*json)); + int type; + int size; + + if (jbv->type == jbvBinary) + { + /* simply copy the whole container and its data */ + JsonContainer *src = (JsonContainer *) jbv->val.binary.data; + JsonContainerData *dst = (JsonContainerData *) &json->root; + + *dst = *src; + dst->data = memcpy(palloc(src->len), src->data, src->len); + + return json; + } + + initStringInfo(&buf); + + JsonEncodeJsonbValue(&buf, jbv); + + switch (jbv->type) + { + case jbvArray: + type = JB_FARRAY; + size = jbv->val.array.nElems; + break; + + case jbvObject: + type = JB_FOBJECT; + size = jbv->val.object.nPairs; + break; + + default: /* scalar */ + type = JB_FARRAY | JB_FSCALAR; + size = 1; + break; + } + + jsonInitContainer((JsonContainerData *) &json->root, + buf.data, buf.len, type, size); + + return json; +} + +/* Context and semantic actions for JsonGetArraySize() */ +typedef struct JsonGetArraySizeState +{ + int level; + uint32 size; +} JsonGetArraySizeState; + +static void +JsonGetArraySize_array_start(void *state) +{ + ((JsonGetArraySizeState *) state)->level++; +} + +static void +JsonGetArraySize_array_end(void *state) +{ + ((JsonGetArraySizeState *) state)->level--; +} + +static void +JsonGetArraySize_array_element_start(void *state, bool isnull) +{ + JsonGetArraySizeState *s = state; + if (s->level == 1) + s->size++; +} + +/* + * Calculate the size of a json array by iterating through its elements. + */ +uint32 +JsonGetArraySize(JsonContainer *jc) +{ + JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, false); + JsonSemAction sem; + JsonGetArraySizeState state; + + state.level = 0; + state.size = 0; + + memset(&sem, 0, sizeof(sem)); + sem.semstate = &state; + sem.array_start = JsonGetArraySize_array_start; + sem.array_end = JsonGetArraySize_array_end; + sem.array_element_end = JsonGetArraySize_array_element_start; + + json_lex(lex); + parse_array(lex, &sem); + + return state.size; +} + +/* + * Find last key in a json object by name. Returns palloc()'d copy of the + * corresponding value, or NULL if is not found. + */ +static inline JsonbValue * +jsonFindLastKeyInObject(JsonContainer *obj, const JsonbValue *key) +{ + JsonbValue *res = NULL; + JsonbValue jbv; + JsonIterator *it; + JsonbIteratorToken tok; + + Assert(JsonContainerIsObject(obj)); + Assert(key->type == jbvString); + + it = JsonIteratorInit(obj); + + while ((tok = JsonIteratorNext(&it, &jbv, true)) != WJB_DONE) + { + if (tok == WJB_KEY && !lengthCompareJsonbStringValue(key, &jbv)) + { + if (!res) + res = palloc(sizeof(*res)); + + tok = JsonIteratorNext(&it, res, true); + Assert(tok == WJB_VALUE); + } + } + + return res; +} + +/* + * Find scalar element in a array. Returns palloc()'d copy of value or NULL. + */ +static JsonbValue * +jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem) +{ + JsonbValue *val = palloc(sizeof(*val)); + JsonIterator *it; + JsonbIteratorToken tok; + + Assert(JsonContainerIsArray(array)); + Assert(IsAJsonbScalar(elem)); + + it = JsonIteratorInit(array); + + while ((tok = JsonIteratorNext(&it, val, true)) != WJB_DONE) + { + if (tok == WJB_ELEM && val->type == elem->type && + equalsJsonbScalarValue(val, (JsonbValue *) elem)) + { + JsonIteratorFree(it); + return val; + } + } + + pfree(val); + return NULL; +} + +/* + * Find value in object (i.e. the "value" part of some key/value pair in an + * object), or find a matching element if we're looking through an array. + * The "flags" argument allows the caller to specify which container types are + * of interest. If we cannot find the value, return NULL. Otherwise, return + * palloc()'d copy of value. + * + * For more details, see findJsonbValueFromContainer(). + */ +JsonbValue * +findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key) +{ + Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0); + + if (!JsonContainerSize(jc)) + return NULL; + + if ((flags & JB_FARRAY) && JsonContainerIsArray(jc)) + return jsonFindValueInArray(jc, key); + + if ((flags & JB_FOBJECT) && JsonContainerIsObject(jc)) + return jsonFindLastKeyInObject(jc, key); + + /* Not found */ + return NULL; +} + +/* + * Get i-th element of a json array. + * + * Returns palloc()'d copy of the value, or NULL if it does not exist. + */ +JsonbValue * +getIthJsonValueFromContainer(JsonContainer *array, uint32 index) +{ + JsonbValue *val = palloc(sizeof(JsonbValue)); + JsonIterator *it; + JsonbIteratorToken tok; + + Assert(JsonContainerIsArray(array)); + + it = JsonIteratorInit(array); + + while ((tok = JsonIteratorNext(&it, val, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + { + if (index-- == 0) + { + JsonIteratorFree(it); + return val; + } + } + } + + pfree(val); + + return NULL; +} + +/* + * Push json JsonbValue into JsonbParseState. + * + * Used for converting an in-memory JsonbValue to a json. For more details, + * see pushJsonbValue(). This function differs from pushJsonbValue() only by + * resetting "uniquify" flag in objects. + */ +JsonbValue * +pushJsonValue(JsonbParseState **pstate, JsonbIteratorToken seq, + JsonbValue *jbval) +{ + JsonIterator *it; + JsonbValue *res = NULL; + JsonbValue v; + JsonbIteratorToken tok; + + if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) || + jbval->type != jbvBinary) + { + /* drop through */ + res = pushJsonbValueScalar(pstate, seq, jbval); + + /* reset "uniquify" flag of objects */ + if (seq == WJB_BEGIN_OBJECT) + (*pstate)->contVal.val.object.uniquify = false; + + return res; + } + + /* unpack the binary and add each piece to the pstate */ + it = JsonIteratorInit((JsonContainer *) jbval->val.binary.data); + while ((tok = JsonIteratorNext(&it, &v, false)) != WJB_DONE) + { + res = pushJsonbValueScalar(pstate, tok, + tok < WJB_BEGIN_ARRAY ? &v : NULL); + + /* reset "uniquify" flag of objects */ + if (tok == WJB_BEGIN_OBJECT) + (*pstate)->contVal.val.object.uniquify = false; + } + + return res; +} + +/* + * Extract scalar JsonbValue from a scalar json. + */ +bool +JsonExtractScalar(JsonContainer *jbc, JsonbValue *res) +{ + JsonIterator *it = JsonIteratorInit(jbc); + JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY; + JsonbValue tmp; + + if (!JsonContainerIsScalar(jbc)) + return false; + + tok = JsonIteratorNext(&it, &tmp, true); + Assert(tok == WJB_BEGIN_ARRAY); + Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar); + + tok = JsonIteratorNext(&it, res, true); + Assert(tok == WJB_ELEM); + Assert(IsAJsonbScalar(res)); + + tok = JsonIteratorNext(&it, &tmp, true); + Assert(tok == WJB_END_ARRAY); + + return true; +} + +/* + * Turn a Json into its C-string representation with stripping quotes from + * scalar strings. + */ +char * +JsonUnquote(Json *jb) +{ + if (JsonContainerIsScalar(&jb->root)) + { + JsonbValue v; + + JsonExtractScalar(&jb->root, &v); + + if (v.type == jbvString) + return pnstrdup(v.val.string.val, v.val.string.len); + } + + return pnstrdup(jb->root.data, jb->root.len); +} + +/* + * Turn a JsonContainer into its C-string representation. + */ +char * +JsonToCString(StringInfo out, JsonContainer *jc, int estimated_len) +{ + if (out) + { + appendBinaryStringInfo(out, jc->data, jc->len); + return out->data; + } + else + { + char *str = palloc(jc->len + 1); + + memcpy(str, jc->data, jc->len); + str[jc->len] = 0; + + return str; + } +} diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 966e0fc0a7..8de67dab35 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -37,9 +37,7 @@ #define JSONB_MAX_PAIRS (Min(MaxAllocSize / sizeof(JsonbPair), JB_CMASK)) static void fillJsonbValue(JsonbContainer *container, int index, - char *base_addr, uint32 offset, - JsonbValue *result); -static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b); + char *base_addr, uint32 offset, JsonbValue *result); static int compareJsonbScalarValue(JsonbValue *a, JsonbValue *b); static Jsonb *convertToJsonb(JsonbValue *val); static void convertJsonbValue(StringInfo buffer, JEntry *header, JsonbValue *val, int level); @@ -58,12 +56,8 @@ static JsonbParseState *pushState(JsonbParseState **pstate); static void appendKey(JsonbParseState *pstate, JsonbValue *scalarVal); static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal); static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal); -static int lengthCompareJsonbStringValue(const void *a, const void *b); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); static void uniqueifyJsonbObject(JsonbValue *object); -static JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, - JsonbIteratorToken seq, - JsonbValue *scalarVal); /* * Turn an in-memory JsonbValue into a Jsonb for on-disk storage. @@ -545,7 +539,7 @@ pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, * Do the actual pushing, with only scalar or pseudo-scalar-array values * accepted. */ -static JsonbValue * +JsonbValue * pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *scalarVal) { @@ -583,6 +577,7 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, (*pstate)->size = 4; (*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) * (*pstate)->size); + (*pstate)->contVal.val.object.uniquify = true; break; case WJB_KEY: Assert(scalarVal->type == jbvString); @@ -825,6 +820,7 @@ JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested) /* Set v to object on first object call */ val->type = jbvObject; val->val.object.nPairs = (*it)->nElems; + val->val.object.uniquify = true; /* * v->val.object.pairs is not actually set, because we aren't @@ -1298,7 +1294,7 @@ JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash, /* * Are two scalar JsonbValues of the same type a and b equal? */ -static bool +bool equalsJsonbScalarValue(JsonbValue *aScalar, JsonbValue *bScalar) { if (aScalar->type == bScalar->type) @@ -1769,7 +1765,7 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) * a and b are first sorted based on their length. If a tie-breaker is * required, only then do we consider string binary equality. */ -static int +int lengthCompareJsonbStringValue(const void *a, const void *b) { const JsonbValue *va = (const JsonbValue *) a; @@ -1833,6 +1829,9 @@ uniqueifyJsonbObject(JsonbValue *object) Assert(object->type == jbvObject); + if (!object->val.object.uniquify) + return; + if (object->val.object.nPairs > 1) qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair), lengthCompareJsonbPair, &hasNonUniq); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index b47384f391..f0689cca1a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -78,6 +78,11 @@ #include "utils/timestamp.h" #include "utils/varlena.h" +#ifdef JSONPATH_JSON_C +#define JSONXOID JSONOID +#else +#define JSONXOID JSONBOID +#endif typedef enum JsonItemType { @@ -2126,7 +2131,12 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, /* construct object id from its base object and offset inside that */ id = jb->type != jbvBinary ? 0 : +#ifdef JSONPATH_JSON_C + (int64) ((char *) ((JsonContainer *) jbc)->data - + (char *) cxt->baseObject.jbc->data); +#else (int64) ((char *) jbc - (char *) cxt->baseObject.jbc); +#endif id += (int64) cxt->baseObject.id * INT64CONST(10000000000); idval.type = jbvNumeric; @@ -2326,7 +2336,7 @@ JsonbArraySize(JsonItem *jb) if (jb->type == jbvBinary) { - JsonbContainer *jbc = jb->jbv.val.binary.data; + JsonbContainer *jbc = (void *) jb->jbv.val.binary.data; if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) return JsonContainerSize(jbc); @@ -2652,6 +2662,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) return result; } +#ifndef JSONPATH_JSON_C /* * Initialize a binary JsonbValue with the given jsonb container. */ @@ -2664,6 +2675,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } +#endif /* * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is. diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c new file mode 100644 index 0000000000..498e464c8c --- /dev/null +++ b/src/backend/utils/adt/jsonpath_json.c @@ -0,0 +1,26 @@ +#define JSONPATH_JSON_C + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "utils/json.h" +#include "utils/jsonapi.h" +#include "utils/jsonb.h" +#include "utils/builtins.h" + +#include "utils/jsonpath_json.h" + +#define jsonb_path_exists json_path_exists +#define jsonb_path_exists_opr json_path_exists_opr +#define jsonb_path_match json_path_match +#define jsonb_path_match_opr json_path_match_opr +#define jsonb_path_query json_path_query +#define jsonb_path_query_novars json_path_query_novars +#define jsonb_path_query_array json_path_query_array +#define jsonb_path_query_array_novars json_path_query_array_novars +#define jsonb_path_query_first json_path_query_first +#define jsonb_path_query_first_novars json_path_query_first_novars +#define jsonb_path_query_first_text json_path_query_first_text +#define jsonb_path_query_first_text_novars json_path_query_first_text_novars + +#include "jsonpath_exec.c" diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index bacafa5183..ccb1eebed2 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3263,5 +3263,13 @@ oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath', oprresult => 'bool', oprcode => 'jsonb_path_match_opr(jsonb,jsonpath)', oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6071', descr => 'jsonpath exists', + oprname => '@?', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'bool', oprcode => 'json_path_exists(json,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6108', descr => 'jsonpath predicate', + oprname => '@@', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'bool', oprcode => 'json_path_match(json,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index ae879d50ad..f9f3837b04 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9290,6 +9290,36 @@ proname => 'jsonb_path_match_opr', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_opr' }, +{ oid => '6043', descr => 'implementation of @? operator', + proname => 'json_path_exists', prorettype => 'bool', + proargtypes => 'json jsonpath', prosrc => 'json_path_exists_opr' }, +{ oid => '6047', descr => 'implementation of @@ operator', + proname => 'json_path_match', prorettype => 'bool', + proargtypes => 'json jsonpath', prosrc => 'json_path_match_opr' }, + +{ oid => '6045', descr => 'jsonpath exists test', + proname => 'json_path_exists', prorettype => 'bool', + proargtypes => 'json jsonpath json bool', prosrc => 'json_path_exists' }, +{ oid => '6046', descr => 'jsonpath query', + proname => 'json_path_query', prorows => '1000', proretset => 't', + prorettype => 'json', proargtypes => 'json jsonpath json bool', + prosrc => 'json_path_query' }, +{ oid => '6129', descr => 'jsonpath query with conditional wrapper', + proname => 'json_path_query_array', prorettype => 'json', + proargtypes => 'json jsonpath json bool', + prosrc => 'json_path_query_array' }, +{ oid => '6069', descr => 'jsonpath match test', + proname => 'json_path_match', prorettype => 'bool', + proargtypes => 'json jsonpath json bool', prosrc => 'json_path_match' }, +{ oid => '6109', descr => 'jsonpath query first item', + proname => 'json_path_query_first', prorettype => 'json', + proargtypes => 'json jsonpath json bool', + prosrc => 'json_path_query_first' }, +{ oid => '6044', descr => 'jsonpath query first item text', + proname => 'json_path_query_first_text', prorettype => 'text', + proargtypes => 'json jsonpath json bool', + prosrc => 'json_path_query_first_text' }, + # txid { oid => '2939', descr => 'I/O', proname => 'txid_snapshot_in', prorettype => 'txid_snapshot', diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 20918f6251..a31c2e9c22 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -15,6 +15,7 @@ #define JSONAPI_H #include "jsonb.h" +#include "access/htup.h" #include "lib/stringinfo.h" typedef enum @@ -93,6 +94,48 @@ typedef struct JsonSemAction json_scalar_action scalar; } JsonSemAction; +typedef enum +{ + JTI_ARRAY_START, + JTI_ARRAY_ELEM, + JTI_ARRAY_ELEM_SCALAR, + JTI_ARRAY_ELEM_AFTER, + JTI_ARRAY_END, + JTI_OBJECT_START, + JTI_OBJECT_KEY, + JTI_OBJECT_VALUE, + JTI_OBJECT_VALUE_AFTER, +} JsontIterState; + +typedef struct JsonContainerData +{ + uint32 header; + int len; + char *data; +} JsonContainerData; + +typedef const JsonContainerData JsonContainer; + +typedef struct Json +{ + JsonContainer root; +} Json; + +typedef struct JsonIterator +{ + struct JsonIterator *parent; + JsonContainer *container; + JsonLexContext *lex; + JsontIterState state; + bool isScalar; +} JsonIterator; + +#define DatumGetJsonP(datum) JsonCreate(DatumGetTextP(datum)) +#define DatumGetJsonPCopy(datum) JsonCreate(DatumGetTextPCopy(datum)) + +#define JsonPGetDatum(json) \ + PointerGetDatum(cstring_to_text_with_len((json)->root.data, (json)->root.len)) + /* * parse_json will parse the string in the lex calling the * action functions in sem at the appropriate points. It is @@ -164,4 +207,22 @@ extern text *transform_json_string_values(text *json, void *action_state, extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); +extern Json *JsonCreate(text *json); +extern JsonbIteratorToken JsonIteratorNext(JsonIterator **pit, JsonbValue *val, + bool skipNested); +extern JsonIterator *JsonIteratorInit(JsonContainer *jc); +extern void JsonIteratorFree(JsonIterator *it); +extern uint32 JsonGetArraySize(JsonContainer *jc); +extern Json *JsonbValueToJson(JsonbValue *jbv); +extern bool JsonExtractScalar(JsonContainer *jbc, JsonbValue *res); +extern char *JsonUnquote(Json *jb); +extern char *JsonToCString(StringInfo out, JsonContainer *jc, + int estimated_len); +extern JsonbValue *pushJsonValue(JsonbParseState **pstate, + JsonbIteratorToken tok, JsonbValue *jbv); +extern JsonbValue *findJsonValueFromContainer(JsonContainer *jc, uint32 flags, + JsonbValue *key); +extern JsonbValue *getIthJsonValueFromContainer(JsonContainer *array, + uint32 index); + #endif /* JSONAPI_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 94e12796b7..b63b4258ed 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -224,10 +224,10 @@ typedef struct } Jsonb; /* convenience macros for accessing the root container in a Jsonb datum */ -#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK) -#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0) -#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0) -#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0) +#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root) +#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root) +#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root) +#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root) enum jbvType @@ -275,6 +275,8 @@ struct JsonbValue { int nPairs; /* 1 pair, 2 elements */ JsonbPair *pairs; + bool uniquify; /* Should we sort pairs by key name and + * remove duplicate keys? */ } object; /* Associative container type */ struct @@ -360,6 +362,8 @@ typedef struct JsonbIterator /* Support functions */ extern uint32 getJsonbOffset(const JsonbContainer *jc, int index); extern uint32 getJsonbLength(const JsonbContainer *jc, int index); +extern int lengthCompareJsonbStringValue(const void *a, const void *b); +extern bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b); extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b); extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader, uint32 flags, @@ -368,6 +372,9 @@ extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader, uint32 i); extern JsonbValue *pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *jbVal); +extern JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, + JsonbIteratorToken seq, + JsonbValue *scalarVal); extern JsonbIterator *JsonbIteratorInit(JsonbContainer *container); extern JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val, bool skipNested); diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h new file mode 100644 index 0000000000..064d77ef6b --- /dev/null +++ b/src/include/utils/jsonpath_json.h @@ -0,0 +1,106 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_json.h + * Jsonpath support for json datatype + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/utils/jsonpath_json.h + * + *------------------------------------------------------------------------- + */ + +#ifndef JSONPATH_JSON_H +#define JSONPATH_JSON_H + +/* redefine jsonb structures */ +#define Jsonb Json +#define JsonbContainer JsonContainer +#define JsonbIterator JsonIterator + +/* redefine jsonb functions */ +#define findJsonbValueFromContainer(jc, flags, jbv) \ + findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv) +#define getIthJsonbValueFromContainer(jc, i) \ + getIthJsonValueFromContainer((JsonContainer *)(jc), i) +#define pushJsonbValue pushJsonValue +#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc)) +#define JsonbIteratorNext JsonIteratorNext +#define JsonbValueToJsonb JsonbValueToJson +#define JsonbToCString JsonToCString +#define JsonbUnquote JsonUnquote +#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv) + +/* redefine jsonb macros */ +#undef JsonContainerSize +#define JsonContainerSize(jc) \ + ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \ + JsonContainerIsArray(jc) \ + ? JsonGetArraySize((JsonContainer *)(jc)) \ + : ((JsonContainer *)(jc))->header & JB_CMASK) + + +#undef DatumGetJsonbP +#define DatumGetJsonbP(d) DatumGetJsonP(d) + +#undef DatumGetJsonbPCopy +#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d) + +#undef JsonbPGetDatum +#define JsonbPGetDatum(json) JsonPGetDatum(json) + +#undef PG_GETARG_JSONB_P +#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n)) + +#undef PG_GETARG_JSONB_P_COPY +#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n)) + +#undef PG_RETURN_JSONB_P +#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json)) + + +#ifdef DatumGetJsonb +#undef DatumGetJsonb +#define DatumGetJsonb(d) DatumGetJsonbP(d) +#endif + +#ifdef DatumGetJsonbCopy +#undef DatumGetJsonbCopy +#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d) +#endif + +#ifdef JsonbGetDatum +#undef JsonbGetDatum +#define JsonbGetDatum(json) JsonbPGetDatum(json) +#endif + +#ifdef PG_GETARG_JSONB +#undef PG_GETARG_JSONB +#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n) +#endif + +#ifdef PG_GETARG_JSONB_COPY +#undef PG_GETARG_JSONB_COPY +#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n) +#endif + +#ifdef PG_RETURN_JSONB +#undef PG_RETURN_JSONB +#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json) +#endif + +/* redefine global jsonpath functions */ +#define executeJsonPath executeJsonPathJson + +static inline JsonbValue * +JsonbInitBinary(JsonbValue *jbv, Json *jb) +{ + jbv->type = jbvBinary; + jbv->val.binary.data = (void *) &jb->root; + jbv->val.binary.len = jb->root.len; + + return jbv; +} + +#endif /* JSONPATH_JSON_H */ diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out new file mode 100644 index 0000000000..926d835473 --- /dev/null +++ b/src/test/regress/expected/json_jsonpath.out @@ -0,0 +1,2097 @@ +select json '{"a": 12}' @? '$'; + ?column? +---------- + t +(1 row) + +select json '{"a": 12}' @? '1'; + ?column? +---------- + t +(1 row) + +select json '{"a": 12}' @? '$.a.b'; + ?column? +---------- + f +(1 row) + +select json '{"a": 12}' @? '$.b'; + ?column? +---------- + f +(1 row) + +select json '{"a": 12}' @? '$.a + 2'; + ?column? +---------- + t +(1 row) + +select json '{"a": 12}' @? '$.b + 2'; + ?column? +---------- + +(1 row) + +select json '{"a": {"a": 12}}' @? '$.a.a'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"a": 12}}' @? '$.*.a'; + ?column? +---------- + t +(1 row) + +select json '{"b": {"a": 12}}' @? '$.*.a'; + ?column? +---------- + t +(1 row) + +select json '{"b": {"a": 12}}' @? '$.*.b'; + ?column? +---------- + f +(1 row) + +select json '{"b": {"a": 12}}' @? 'strict $.*.b'; + ?column? +---------- + +(1 row) + +select json '{}' @? '$.*'; + ?column? +---------- + f +(1 row) + +select json '{"a": 1}' @? '$.*'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? 'lax $.**{1}'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? 'lax $.**{2}'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? 'lax $.**{3}'; + ?column? +---------- + f +(1 row) + +select json '[]' @? '$[*]'; + ?column? +---------- + f +(1 row) + +select json '[1]' @? '$[*]'; + ?column? +---------- + t +(1 row) + +select json '[1]' @? '$[1]'; + ?column? +---------- + f +(1 row) + +select json '[1]' @? 'strict $[1]'; + ?column? +---------- + +(1 row) + +select json_path_query('[1]', 'strict $[1]'); +ERROR: jsonpath array subscript is out of bounds +select json '[1]' @? 'lax $[10000000000000000]'; + ?column? +---------- + +(1 row) + +select json '[1]' @? 'strict $[10000000000000000]'; + ?column? +---------- + +(1 row) + +select json_path_query('[1]', 'lax $[10000000000000000]'); +ERROR: jsonpath array subscript is out of integer range +select json_path_query('[1]', 'strict $[10000000000000000]'); +ERROR: jsonpath array subscript is out of integer range +select json '[1]' @? '$[0]'; + ?column? +---------- + t +(1 row) + +select json '[1]' @? '$[0.3]'; + ?column? +---------- + t +(1 row) + +select json '[1]' @? '$[0.5]'; + ?column? +---------- + t +(1 row) + +select json '[1]' @? '$[0.9]'; + ?column? +---------- + t +(1 row) + +select json '[1]' @? '$[1.2]'; + ?column? +---------- + f +(1 row) + +select json '[1]' @? 'strict $[1.2]'; + ?column? +---------- + +(1 row) + +select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; + ?column? +---------- + f +(1 row) + +select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- + t +(1 row) + +select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- + t +(1 row) + +select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])'; + ?column? +---------- + f +(1 row) + +select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- + t +(1 row) + +select json '1' @? '$ ? ((@ == "1") is unknown)'; + ?column? +---------- + t +(1 row) + +select json '1' @? '$ ? ((@ == 1) is unknown)'; + ?column? +---------- + f +(1 row) + +select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)'; + ?column? +---------- + t +(1 row) + +select json_path_query('1', 'lax $.a'); + json_path_query +----------------- +(0 rows) + +select json_path_query('1', 'strict $.a'); +ERROR: jsonpath member accessor can only be applied to an object +select json_path_query('1', 'strict $.*'); +ERROR: jsonpath wildcard member accessor can only be applied to an object +select json_path_query('[]', 'lax $.a'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'strict $.a'); +ERROR: jsonpath member accessor can only be applied to an object +select json_path_query('{}', 'lax $.a'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{}', 'strict $.a'); +ERROR: JSON object does not contain key "a" +select json_path_query('1', 'strict $[1]'); +ERROR: jsonpath array accessor can only be applied to an array +select json_path_query('1', 'strict $[*]'); +ERROR: jsonpath wildcard array accessor can only be applied to an array +select json_path_query('[]', 'strict $[1]'); +ERROR: jsonpath array subscript is out of bounds +select json_path_query('[]', 'strict $["a"]'); +ERROR: jsonpath array subscript is not a single numeric value +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.a'); + json_path_query +----------------- + 12 +(1 row) + +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.b'); + json_path_query +----------------- + {"a": 13} +(1 row) + +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.*'); + json_path_query +----------------- + 12 + {"a": 13} +(2 rows) + +select json_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*'); + json_path_query +----------------- + 13 + 14 +(2 rows) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a'); +ERROR: division by zero +select json_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]'); + json_path_query +----------------- + {"a": 13} + {"b": 14} + "ccc" +(3 rows) + +select json_path_query('1', 'lax $[0]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('1', 'lax $[*]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1]', 'lax $[0]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1]', 'lax $[*]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1,2,3]', 'lax $[*]'); + json_path_query +----------------- + 1 + 2 + 3 +(3 rows) + +select json_path_query('[1,2,3]', 'strict $[*].a'); +ERROR: jsonpath member accessor can only be applied to an object +select json_path_query('[]', '$[last]'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', '$[last ? (exists(last))]'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'strict $[last]'); +ERROR: jsonpath array subscript is out of bounds +select json_path_query('[1]', '$[last]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1,2,3]', '$[last]'); + json_path_query +----------------- + 3 +(1 row) + +select json_path_query('[1,2,3]', '$[last - 1]'); + json_path_query +----------------- + 2 +(1 row) + +select json_path_query('[1,2,3]', '$[last ? (@.type() == "number")]'); + json_path_query +----------------- + 3 +(1 row) + +select json_path_query('[1,2,3]', '$[last ? (@.type() == "string")]'); +ERROR: jsonpath array subscript is not a single numeric value +select * from json_path_query('{"a": 10}', '$'); + json_path_query +----------------- + {"a": 10} +(1 row) + +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)'); +ERROR: could not find jsonpath variable "value" +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '1'); +ERROR: "vars" argument is not an object +DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object. +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]'); +ERROR: "vars" argument is not an object +DETAIL: Jsonpath parameters should be encoded as key-value pairs of "vars" object. +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}'); + json_path_query +----------------- + {"a": 10} +(1 row) + +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}'); + json_path_query +----------------- +(0 rows) + +select * from json_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); + json_path_query +----------------- + 10 +(1 row) + +select * from json_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); + json_path_query +----------------- + 10 + 11 + 12 +(3 rows) + +select * from json_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}'); + json_path_query +----------------- + 10 + 11 +(2 rows) + +select * from json_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); + json_path_query +----------------- + 10 + 11 + 12 +(3 rows) + +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); + json_path_query +----------------- + "1" +(1 row) + +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); + json_path_query +----------------- + "1" +(1 row) + +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}'); + json_path_query +----------------- + null +(1 row) + +select * from json_path_query('[1, "2", null]', '$[*] ? (@ != null)'); + json_path_query +----------------- + 1 + "2" +(2 rows) + +select * from json_path_query('[1, "2", null]', '$[*] ? (@ == null)'); + json_path_query +----------------- + null +(1 row) + +select * from json_path_query('{}', '$ ? (@ == @)'); + json_path_query +----------------- +(0 rows) + +select * from json_path_query('[]', 'strict $ ? (@ == @)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**'); + json_path_query +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}'); + json_path_query +----------------- + {"a": {"b": 1}} +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}'); + json_path_query +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}'); + json_path_query +----------------- + {"b": 1} +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}'); + json_path_query +----------------- + {"b": 1} + 1 +(2 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{2}'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{last}'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)'); + json_path_query +----------------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)'; + ?column? +---------- + f +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)'; + ?column? +---------- + f +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; + ?column? +---------- + f +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))'); + json_path_query +----------------- + {"x": 2} +(1 row) + +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); + json_path_query +----------------- + {"x": 2} +(1 row) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))'); + json_path_query +----------------- + {"x": 2} +(1 row) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)'); + json_path_query +----------------- + {"x": 2} + {"y": 3} +(2 rows) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))'); + json_path_query +----------------- + {"x": 2} +(1 row) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)'); + json_path_query +----------------- + {"y": 3} +(1 row) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)'); + json_path_query +---------------------- + [{"x": 2}, {"y": 3}] +(1 row) + +--test ternary logic +select + x, y, + json_path_query( + '[true, false, null]', + '$[*] ? (@ == true && ($x == true && $y == true) || + @ == false && !($x == true && $y == true) || + @ == null && ($x == true && $y == true) is unknown)', + json_build_object('x', x, 'y', y) + ) as "x && y" +from + (values (json 'true'), ('false'), ('"null"')) x(x), + (values (json 'true'), ('false'), ('"null"')) y(y); + x | y | x && y +--------+--------+-------- + true | true | true + true | false | false + true | "null" | null + false | true | false + false | false | false + false | "null" | false + "null" | true | null + "null" | false | false + "null" | "null" | null +(9 rows) + +select + x, y, + json_path_query( + '[true, false, null]', + '$[*] ? (@ == true && ($x == true || $y == true) || + @ == false && !($x == true || $y == true) || + @ == null && ($x == true || $y == true) is unknown)', + json_build_object('x', x, 'y', y) + ) as "x || y" +from + (values (json 'true'), ('false'), ('"null"')) x(x), + (values (json 'true'), ('false'), ('"null"')) y(y); + x | y | x || y +--------+--------+-------- + true | true | true + true | false | true + true | "null" | true + false | true | true + false | false | false + false | "null" | null + "null" | true | true + "null" | false | null + "null" | "null" | null +(9 rows) + +select json '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)'; + ?column? +---------- + f +(1 row) + +select json '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)'; + ?column? +---------- + t +(1 row) + +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)'); + json_path_query +----------------- + {"a": 2, "b":1} +(1 row) + +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))'); + json_path_query +----------------- + {"a": 2, "b":1} +(1 row) + +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)'); + json_path_query +----------------- + {"a": 2, "b":1} +(1 row) + +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))'); + json_path_query +----------------- + {"a": 2, "b":1} +(1 row) + +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)'; + ?column? +---------- + t +(1 row) + +select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)'; + ?column? +---------- + t +(1 row) + +select json '[1,2,3]' @? '$ ? (+@[*] > +2)'; + ?column? +---------- + t +(1 row) + +select json '[1,2,3]' @? '$ ? (+@[*] > +3)'; + ?column? +---------- + f +(1 row) + +select json '[1,2,3]' @? '$ ? (-@[*] < -2)'; + ?column? +---------- + t +(1 row) + +select json '[1,2,3]' @? '$ ? (-@[*] < -3)'; + ?column? +---------- + f +(1 row) + +select json '1' @? '$ ? ($ > 0)'; + ?column? +---------- + t +(1 row) + +-- arithmetic errors +select json_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)'); + json_path_query +----------------- + 1 + 2 + 3 +(3 rows) + +select json_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)'); + json_path_query +----------------- + 0 +(1 row) + +select json_path_query('0', '1 / $'); +ERROR: division by zero +select json_path_query('0', '1 / $ + 2'); +ERROR: division by zero +select json_path_query('0', '-(3 + 1 % $)'); +ERROR: division by zero +select json_path_query('1', '$ + "2"'); +ERROR: right operand of jsonpath operator + is not a single numeric value +select json_path_query('[1, 2]', '3 * $'); +ERROR: right operand of jsonpath operator * is not a single numeric value +select json_path_query('"a"', '-$'); +ERROR: operand of unary jsonpath operator - is not a numeric value +select json_path_query('[1,"2",3]', '+$'); +ERROR: operand of unary jsonpath operator + is not a numeric value +select json '["1",2,0,3]' @? '-$[*]'; + ?column? +---------- + t +(1 row) + +select json '[1,"2",0,3]' @? '-$[*]'; + ?column? +---------- + t +(1 row) + +select json '["1",2,0,3]' @? 'strict -$[*]'; + ?column? +---------- + +(1 row) + +select json '[1,"2",0,3]' @? 'strict -$[*]'; + ?column? +---------- + +(1 row) + +-- unwrapping of operator arguments in lax mode +select json_path_query('{"a": [2]}', 'lax $.a * 3'); + json_path_query +----------------- + 6 +(1 row) + +select json_path_query('{"a": [2]}', 'lax $.a + 3'); + json_path_query +----------------- + 5 +(1 row) + +select json_path_query('{"a": [2, 3, 4]}', 'lax -$.a'); + json_path_query +----------------- + -2 + -3 + -4 +(3 rows) + +-- should fail +select json_path_query('{"a": [1, 2]}', 'lax $.a * 3'); +ERROR: left operand of jsonpath operator * is not a single numeric value +-- extension: boolean expressions +select json_path_query('2', '$ > 1'); + json_path_query +----------------- + true +(1 row) + +select json_path_query('2', '$ <= 1'); + json_path_query +----------------- + false +(1 row) + +select json_path_query('2', '$ == "2"'); + json_path_query +----------------- + null +(1 row) + +select json '2' @? '$ == "2"'; + ?column? +---------- + t +(1 row) + +select json '2' @@ '$ > 1'; + ?column? +---------- + t +(1 row) + +select json '2' @@ '$ <= 1'; + ?column? +---------- + f +(1 row) + +select json '2' @@ '$ == "2"'; + ?column? +---------- + +(1 row) + +select json '2' @@ '1'; + ?column? +---------- + +(1 row) + +select json '{}' @@ '$'; + ?column? +---------- + +(1 row) + +select json '[]' @@ '$'; + ?column? +---------- + +(1 row) + +select json '[1,2,3]' @@ '$[*]'; + ?column? +---------- + +(1 row) + +select json '[]' @@ '$[*]'; + ?column? +---------- + +(1 row) + +select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); + json_path_match +----------------- + f +(1 row) + +select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + json_path_match +----------------- + t +(1 row) + +select json_path_query('[null,1,true,"a",[],{}]', '$.type()'); + json_path_query +----------------- + "array" +(1 row) + +select json_path_query('[null,1,true,"a",[],{}]', 'lax $.type()'); + json_path_query +----------------- + "array" +(1 row) + +select json_path_query('[null,1,true,"a",[],{}]', '$[*].type()'); + json_path_query +----------------- + "null" + "number" + "boolean" + "string" + "array" + "object" +(6 rows) + +select json_path_query('null', 'null.type()'); + json_path_query +----------------- + "null" +(1 row) + +select json_path_query('null', 'true.type()'); + json_path_query +----------------- + "boolean" +(1 row) + +select json_path_query('null', '123.type()'); + json_path_query +----------------- + "number" +(1 row) + +select json_path_query('null', '"123".type()'); + json_path_query +----------------- + "string" +(1 row) + +select json_path_query('{"a": 2}', '($.a - 5).abs() + 10'); + json_path_query +----------------- + 13 +(1 row) + +select json_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3'); + json_path_query +----------------- + -1.7 +(1 row) + +select json_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)'); + json_path_query +----------------- + true +(1 row) + +select json_path_query('[1, 2, 3]', '($[*] > 3).type()'); + json_path_query +----------------- + "boolean" +(1 row) + +select json_path_query('[1, 2, 3]', '($[*].a > 3).type()'); + json_path_query +----------------- + "boolean" +(1 row) + +select json_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()'); + json_path_query +----------------- + "null" +(1 row) + +select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); +ERROR: jsonpath item method .size() can only be applied to an array +select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); + json_path_query +----------------- + 1 + 1 + 1 + 1 + 0 + 1 + 3 + 1 + 1 +(9 rows) + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); + json_path_query +----------------- + 0 + 1 + 2 + 3.4 + 5.6 +(5 rows) + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); + json_path_query +----------------- + 0 + 1 + -2 + -4 + 5 +(5 rows) + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); + json_path_query +----------------- + 0 + 1 + -2 + -3 + 6 +(5 rows) + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); + json_path_query +----------------- + 0 + 1 + 2 + 3 + 6 +(5 rows) + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); + json_path_query +----------------- + "number" + "number" + "number" + "number" + "number" +(5 rows) + +select json_path_query('[{},1]', '$[*].keyvalue()'); +ERROR: jsonpath item method .keyvalue() can only be applied to an object +select json_path_query('{}', '$.keyvalue()'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); + json_path_query +---------------------------------------------- + {"key": "a", "value": 1, "id": 0} + {"key": "b", "value": [1, 2], "id": 0} + {"key": "c", "value": {"a": "bbb"}, "id": 0} +(3 rows) + +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); + json_path_query +----------------------------------------------- + {"key": "a", "value": 1, "id": 1} + {"key": "b", "value": [1, 2], "id": 1} + {"key": "c", "value": {"a": "bbb"}, "id": 24} +(3 rows) + +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); +ERROR: jsonpath item method .keyvalue() can only be applied to an object +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); + json_path_query +----------------------------------------------- + {"key": "a", "value": 1, "id": 1} + {"key": "b", "value": [1, 2], "id": 1} + {"key": "c", "value": {"a": "bbb"}, "id": 24} +(3 rows) + +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a'); +ERROR: jsonpath item method .keyvalue() can only be applied to an object +select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()'; + ?column? +---------- + t +(1 row) + +select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key'; + ?column? +---------- + t +(1 row) + +select json_path_query('null', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a string or numeric value +select json_path_query('true', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a string or numeric value +select json_path_query('[]', '$.double()'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'strict $.double()'); +ERROR: jsonpath item method .double() can only be applied to a string or numeric value +select json_path_query('{}', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a string or numeric value +select json_path_query('1.23', '$.double()'); + json_path_query +----------------- + 1.23 +(1 row) + +select json_path_query('"1.23"', '$.double()'); + json_path_query +----------------- + 1.23 +(1 row) + +select json_path_query('"1.23aaa"', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a numeric value +select json_path_query('"nan"', '$.double()'); + json_path_query +----------------- + "NaN" +(1 row) + +select json_path_query('"NaN"', '$.double()'); + json_path_query +----------------- + "NaN" +(1 row) + +select json_path_query('"inf"', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a numeric value +select json_path_query('"-inf"', '$.double()'); +ERROR: jsonpath item method .double() can only be applied to a numeric value +select json_path_query('{}', '$.abs()'); +ERROR: jsonpath item method .abs() can only be applied to a numeric value +select json_path_query('true', '$.floor()'); +ERROR: jsonpath item method .floor() can only be applied to a numeric value +select json_path_query('"1.2"', '$.ceiling()'); +ERROR: jsonpath item method .ceiling() can only be applied to a numeric value +select json_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); + json_path_query +----------------- + "abc" + "abcabc" +(2 rows) + +select json_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); + json_path_query +---------------------------- + ["", "a", "abc", "abcabc"] +(1 row) + +select json_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); + json_path_query +----------------- +(0 rows) + +select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); + json_path_query +----------------- +(0 rows) + +select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); + json_path_query +---------------------------- + ["abc", "abcabc", null, 1] +(1 row) + +select json_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); + json_path_query +---------------------------- + [null, 1, "abc", "abcabc"] +(1 row) + +select json_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); + json_path_query +---------------------------- + [null, 1, "abd", "abdabc"] +(1 row) + +select json_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); + json_path_query +----------------- + null + 1 +(2 rows) + +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); + json_path_query +----------------- + "abc" + "abdacb" +(2 rows) + +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")'); + json_path_query +----------------- + "abc" + "aBdC" + "abdacb" +(3 rows) + +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")'); + json_path_query +----------------- + "abc" + "abdacb" + "adc\nabc" +(3 rows) + +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")'); + json_path_query +----------------- + "abc" + "abdacb" +(2 rows) + +select json_path_query('null', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select json_path_query('true', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select json_path_query('1', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select json_path_query('[]', '$.datetime()'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'strict $.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select json_path_query('{}', '$.datetime()'); +ERROR: jsonpath item method .datetime() can only be applied to a string value +select json_path_query('""', '$.datetime()'); +ERROR: unrecognized datetime format +HINT: use datetime template argument for explicit format specification +select json_path_query('"12:34"', '$.datetime("aaa")'); +ERROR: datetime format is not dated and not timed +select json_path_query('"12:34"', '$.datetime("aaa", 1)'); +ERROR: datetime format is not dated and not timed +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)'); + json_path_query +--------------------- + "12:34:00+00:00:01" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)'); + json_path_query +------------------------- + "12:34:00+596523:14:07" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); + json_path_query +------------------------- + "12:34:00-596523:14:07" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); +ERROR: timezone argument of jsonpath item method .datetime() is out of integer range +select json_path_query('"aaaa"', '$.datetime("HH24")'); +ERROR: invalid value "aa" for "HH24" +DETAIL: Value must be an integer. +select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; + ?column? +---------- + t +(1 row) + +select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); + json_path_query +----------------- + "2017-03-10" +(1 row) + +select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); + json_path_query +----------------- + "date" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); + json_path_query +----------------- + "2017-03-10" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + json_path_query +----------------- + "date" +(1 row) + +select json_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); + json_path_query +------------------------------- + "timestamp without time zone" +(1 row) + +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); + json_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select json_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()'); + json_path_query +-------------------------- + "time without time zone" +(1 row) + +select json_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + json_path_query +----------------------- + "time with time zone" +(1 row) + +set time zone '+00'; +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + json_path_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timestamptz +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'); + json_path_query +-------------------------------- + "2017-03-10T12:34:00-00:12:34" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'); +ERROR: invalid input syntax for type timestamptz: "UTC" +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))'); + json_path_query +-------------------------------- + "2017-03-10T12:34:00-05:12:34" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + json_build_object('tz', extract(timezone from now()))); + json_path_query +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+05:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00-05:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+05:20" +(1 row) + +select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00-05:20" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI")'); + json_path_query +----------------- + "12:34:00" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timetz +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")'); + json_path_query +------------------ + "12:34:00+00:00" +(1 row) + +select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); + json_path_query +------------------ + "12:34:00+05:00" +(1 row) + +select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); + json_path_query +------------------ + "12:34:00-05:00" +(1 row) + +select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + json_path_query +------------------ + "12:34:00+05:20" +(1 row) + +select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + json_path_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone '+10'; +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + json_path_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timestamptz +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + json_build_object('tz', extract(timezone from now()))); + json_path_query +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+05:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00-05:00" +(1 row) + +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+05:20" +(1 row) + +select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + json_path_query +----------------------------- + "2017-03-10T12:34:00-05:20" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI")'); + json_path_query +----------------- + "12:34:00" +(1 row) + +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +ERROR: missing time-zone in input string for type timetz +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")'); + json_path_query +------------------ + "12:34:00+10:00" +(1 row) + +select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); + json_path_query +------------------ + "12:34:00+05:00" +(1 row) + +select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); + json_path_query +------------------ + "12:34:00-05:00" +(1 row) + +select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + json_path_query +------------------ + "12:34:00+05:20" +(1 row) + +select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + json_path_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone default; +select json_path_query('"2017-03-10"', '$.datetime().type()'); + json_path_query +----------------- + "date" +(1 row) + +select json_path_query('"2017-03-10"', '$.datetime()'); + json_path_query +----------------- + "2017-03-10" +(1 row) + +select json_path_query('"2017-03-10 12:34:56"', '$.datetime().type()'); + json_path_query +------------------------------- + "timestamp without time zone" +(1 row) + +select json_path_query('"2017-03-10 12:34:56"', '$.datetime()'); + json_path_query +----------------------- + "2017-03-10T12:34:56" +(1 row) + +select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); + json_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); + json_path_query +----------------------------- + "2017-03-10T12:34:56+03:00" +(1 row) + +select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); + json_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); + json_path_query +----------------------------- + "2017-03-10T12:34:56+03:10" +(1 row) + +select json_path_query('"12:34:56"', '$.datetime().type()'); + json_path_query +-------------------------- + "time without time zone" +(1 row) + +select json_path_query('"12:34:56"', '$.datetime()'); + json_path_query +----------------- + "12:34:56" +(1 row) + +select json_path_query('"12:34:56 +3"', '$.datetime().type()'); + json_path_query +----------------------- + "time with time zone" +(1 row) + +select json_path_query('"12:34:56 +3"', '$.datetime()'); + json_path_query +------------------ + "12:34:56+03:00" +(1 row) + +select json_path_query('"12:34:56 +3:10"', '$.datetime().type()'); + json_path_query +----------------------- + "time with time zone" +(1 row) + +select json_path_query('"12:34:56 +3:10"', '$.datetime()'); + json_path_query +------------------ + "12:34:56+03:10" +(1 row) + +set time zone '+00'; +-- date comparison +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); + json_path_query +----------------------------- + "2017-03-10" + "2017-03-10T00:00:00" + "2017-03-10T03:00:00+03:00" +(3 rows) + +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); + json_path_query +----------------------------- + "2017-03-10" + "2017-03-11" + "2017-03-10T00:00:00" + "2017-03-10T12:34:56" + "2017-03-10T03:00:00+03:00" +(5 rows) + +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); + json_path_query +----------------------------- + "2017-03-09" + "2017-03-10T01:02:03+04:00" +(2 rows) + +-- time comparison +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); + json_path_query +------------------ + "12:35:00" + "12:35:00+00:00" +(2 rows) + +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); + json_path_query +------------------ + "12:35:00" + "12:36:00" + "12:35:00+00:00" +(3 rows) + +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); + json_path_query +------------------ + "12:34:00" + "12:35:00+01:00" + "13:35:00+01:00" +(3 rows) + +-- timetz comparison +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); + json_path_query +------------------ + "12:35:00+01:00" +(1 row) + +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); + json_path_query +------------------ + "12:35:00+01:00" + "12:36:00+01:00" + "12:35:00-02:00" + "11:35:00" + "12:35:00" +(5 rows) + +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); + json_path_query +------------------ + "12:34:00+01:00" + "12:35:00+02:00" + "10:35:00" +(3 rows) + +-- timestamp comparison +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + json_path_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T13:35:00+01:00" +(2 rows) + +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + json_path_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:36:00" + "2017-03-10T13:35:00+01:00" + "2017-03-10T12:35:00-01:00" + "2017-03-11" +(5 rows) + +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + json_path_query +----------------------------- + "2017-03-10T12:34:00" + "2017-03-10T12:35:00+01:00" + "2017-03-10" +(3 rows) + +-- timestamptz comparison +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + json_path_query +----------------------------- + "2017-03-10T12:35:00+01:00" + "2017-03-10T11:35:00" +(2 rows) + +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + json_path_query +----------------------------- + "2017-03-10T12:35:00+01:00" + "2017-03-10T12:36:00+01:00" + "2017-03-10T12:35:00-02:00" + "2017-03-10T11:35:00" + "2017-03-10T12:35:00" + "2017-03-11" +(6 rows) + +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + json_path_query +----------------------------- + "2017-03-10T12:34:00+01:00" + "2017-03-10T12:35:00+02:00" + "2017-03-10T10:35:00" + "2017-03-10" +(4 rows) + +set time zone default; +-- jsonpath operators +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); + json_path_query +----------------- + {"a": 1} + {"a": 2} +(2 rows) + +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); + json_path_query +----------------- +(0 rows) + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); +ERROR: JSON object does not contain key "a" +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); + json_path_query_array +----------------------- + [1, 2] +(1 row) + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); + json_path_query_array +----------------------- + [1] +(1 row) + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); + json_path_query_array +----------------------- + [] +(1 row) + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}'); + json_path_query_array +----------------------- + [2, 3] +(1 row) + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}'); + json_path_query_array +----------------------- + [] +(1 row) + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); +ERROR: JSON object does not contain key "a" +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a'); + json_path_query_first +----------------------- + 1 +(1 row) + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); + json_path_query_first +----------------------- + 1 +(1 row) + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); + json_path_query_first +----------------------- + +(1 row) + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}'); + json_path_query_first +----------------------- + 2 +(1 row) + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}'); + json_path_query_first +----------------------- + +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; + ?column? +---------- + t +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; + ?column? +---------- + f +(1 row) + +SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}'); + json_path_exists +------------------ + t +(1 row) + +SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}'); + json_path_exists +------------------ + f +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; + ?column? +---------- + t +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; + ?column? +---------- + f +(1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f23fe8d870..84e3ec6c91 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -99,7 +99,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 json_jsonpath jsonb_jsonpath # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index ca200eb599..3f363e476c 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -165,6 +165,7 @@ test: jsonb test: json_encoding test: jsonpath test: jsonpath_encoding +test: json_jsonpath test: jsonb_jsonpath test: plancache test: limit diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql new file mode 100644 index 0000000000..73781b0372 --- /dev/null +++ b/src/test/regress/sql/json_jsonpath.sql @@ -0,0 +1,477 @@ +select json '{"a": 12}' @? '$'; +select json '{"a": 12}' @? '1'; +select json '{"a": 12}' @? '$.a.b'; +select json '{"a": 12}' @? '$.b'; +select json '{"a": 12}' @? '$.a + 2'; +select json '{"a": 12}' @? '$.b + 2'; +select json '{"a": {"a": 12}}' @? '$.a.a'; +select json '{"a": {"a": 12}}' @? '$.*.a'; +select json '{"b": {"a": 12}}' @? '$.*.a'; +select json '{"b": {"a": 12}}' @? '$.*.b'; +select json '{"b": {"a": 12}}' @? 'strict $.*.b'; +select json '{}' @? '$.*'; +select json '{"a": 1}' @? '$.*'; +select json '{"a": {"b": 1}}' @? 'lax $.**{1}'; +select json '{"a": {"b": 1}}' @? 'lax $.**{2}'; +select json '{"a": {"b": 1}}' @? 'lax $.**{3}'; +select json '[]' @? '$[*]'; +select json '[1]' @? '$[*]'; +select json '[1]' @? '$[1]'; +select json '[1]' @? 'strict $[1]'; +select json_path_query('[1]', 'strict $[1]'); +select json '[1]' @? 'lax $[10000000000000000]'; +select json '[1]' @? 'strict $[10000000000000000]'; +select json_path_query('[1]', 'lax $[10000000000000000]'); +select json_path_query('[1]', 'strict $[10000000000000000]'); +select json '[1]' @? '$[0]'; +select json '[1]' @? '$[0.3]'; +select json '[1]' @? '$[0.5]'; +select json '[1]' @? '$[0.9]'; +select json '[1]' @? '$[1.2]'; +select json '[1]' @? 'strict $[1.2]'; +select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; +select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])'; +select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])'; +select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])'; +select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])'; +select json '1' @? '$ ? ((@ == "1") is unknown)'; +select json '1' @? '$ ? ((@ == 1) is unknown)'; +select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)'; + +select json_path_query('1', 'lax $.a'); +select json_path_query('1', 'strict $.a'); +select json_path_query('1', 'strict $.*'); +select json_path_query('[]', 'lax $.a'); +select json_path_query('[]', 'strict $.a'); +select json_path_query('{}', 'lax $.a'); +select json_path_query('{}', 'strict $.a'); + +select json_path_query('1', 'strict $[1]'); +select json_path_query('1', 'strict $[*]'); +select json_path_query('[]', 'strict $[1]'); +select json_path_query('[]', 'strict $["a"]'); + +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.a'); +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.b'); +select json_path_query('{"a": 12, "b": {"a": 13}}', '$.*'); +select json_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a'); +select json_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]'); +select json_path_query('1', 'lax $[0]'); +select json_path_query('1', 'lax $[*]'); +select json_path_query('[1]', 'lax $[0]'); +select json_path_query('[1]', 'lax $[*]'); +select json_path_query('[1,2,3]', 'lax $[*]'); +select json_path_query('[1,2,3]', 'strict $[*].a'); +select json_path_query('[]', '$[last]'); +select json_path_query('[]', '$[last ? (exists(last))]'); +select json_path_query('[]', 'strict $[last]'); +select json_path_query('[1]', '$[last]'); +select json_path_query('[1,2,3]', '$[last]'); +select json_path_query('[1,2,3]', '$[last - 1]'); +select json_path_query('[1,2,3]', '$[last ? (@.type() == "number")]'); +select json_path_query('[1,2,3]', '$[last ? (@.type() == "string")]'); + +select * from json_path_query('{"a": 10}', '$'); +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)'); +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '1'); +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '[{"value" : 13}]'); +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}'); +select * from json_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}'); +select * from json_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); +select * from json_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); +select * from json_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $x.value)', '{"x": {"value" : 13}}'); +select * from json_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); +select * from json_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : null}'); +select * from json_path_query('[1, "2", null]', '$[*] ? (@ != null)'); +select * from json_path_query('[1, "2", null]', '$[*] ? (@ == null)'); +select * from json_path_query('{}', '$ ? (@ == @)'); +select * from json_path_query('[]', 'strict $ ? (@ == @)'); + +select json_path_query('{"a": {"b": 1}}', 'lax $.**'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{2}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{last}'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)'); +select json_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); +select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)'); + +select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)'; + +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))'); +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))'); +select json_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x))'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? (exists (@.x + "3"))'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'lax $.g ? ((exists (@.x + "3")) is unknown)'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? (exists (@.x))'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g[*] ? ((exists (@.x)) is unknown)'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? (exists (@[*].x))'); +select json_path_query('{"g": [{"x": 2}, {"y": 3}]}', 'strict $.g ? ((exists (@[*].x)) is unknown)'); + +--test ternary logic +select + x, y, + json_path_query( + '[true, false, null]', + '$[*] ? (@ == true && ($x == true && $y == true) || + @ == false && !($x == true && $y == true) || + @ == null && ($x == true && $y == true) is unknown)', + json_build_object('x', x, 'y', y) + ) as "x && y" +from + (values (json 'true'), ('false'), ('"null"')) x(x), + (values (json 'true'), ('false'), ('"null"')) y(y); + +select + x, y, + json_path_query( + '[true, false, null]', + '$[*] ? (@ == true && ($x == true || $y == true) || + @ == false && !($x == true || $y == true) || + @ == null && ($x == true || $y == true) is unknown)', + json_build_object('x', x, 'y', y) + ) as "x || y" +from + (values (json 'true'), ('false'), ('"null"')) x(x), + (values (json 'true'), ('false'), ('"null"')) y(y); + +select json '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)'; +select json '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)'; +select json '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)'; +select json '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)'; +select json '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)'; +select json '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)'; +select json '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)'; + +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)'); +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))'); +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)'); +select json_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))'); +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)'; +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)'; +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)'; +select json '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)'; +select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)'; +select json '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)'; +select json '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)'; +select json '[1,2,3]' @? '$ ? (+@[*] > +2)'; +select json '[1,2,3]' @? '$ ? (+@[*] > +3)'; +select json '[1,2,3]' @? '$ ? (-@[*] < -2)'; +select json '[1,2,3]' @? '$ ? (-@[*] < -3)'; +select json '1' @? '$ ? ($ > 0)'; + +-- arithmetic errors +select json_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)'); +select json_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)'); +select json_path_query('0', '1 / $'); +select json_path_query('0', '1 / $ + 2'); +select json_path_query('0', '-(3 + 1 % $)'); +select json_path_query('1', '$ + "2"'); +select json_path_query('[1, 2]', '3 * $'); +select json_path_query('"a"', '-$'); +select json_path_query('[1,"2",3]', '+$'); +select json '["1",2,0,3]' @? '-$[*]'; +select json '[1,"2",0,3]' @? '-$[*]'; +select json '["1",2,0,3]' @? 'strict -$[*]'; +select json '[1,"2",0,3]' @? 'strict -$[*]'; + +-- unwrapping of operator arguments in lax mode +select json_path_query('{"a": [2]}', 'lax $.a * 3'); +select json_path_query('{"a": [2]}', 'lax $.a + 3'); +select json_path_query('{"a": [2, 3, 4]}', 'lax -$.a'); +-- should fail +select json_path_query('{"a": [1, 2]}', 'lax $.a * 3'); + +-- extension: boolean expressions +select json_path_query('2', '$ > 1'); +select json_path_query('2', '$ <= 1'); +select json_path_query('2', '$ == "2"'); +select json '2' @? '$ == "2"'; + +select json '2' @@ '$ > 1'; +select json '2' @@ '$ <= 1'; +select json '2' @@ '$ == "2"'; +select json '2' @@ '1'; +select json '{}' @@ '$'; +select json '[]' @@ '$'; +select json '[1,2,3]' @@ '$[*]'; +select json '[]' @@ '$[*]'; +select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); +select json_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + +select json_path_query('[null,1,true,"a",[],{}]', '$.type()'); +select json_path_query('[null,1,true,"a",[],{}]', 'lax $.type()'); +select json_path_query('[null,1,true,"a",[],{}]', '$[*].type()'); +select json_path_query('null', 'null.type()'); +select json_path_query('null', 'true.type()'); +select json_path_query('null', '123.type()'); +select json_path_query('null', '"123".type()'); + +select json_path_query('{"a": 2}', '($.a - 5).abs() + 10'); +select json_path_query('{"a": 2.5}', '-($.a * $.a).floor() % 4.3'); +select json_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)'); +select json_path_query('[1, 2, 3]', '($[*] > 3).type()'); +select json_path_query('[1, 2, 3]', '($[*].a > 3).type()'); +select json_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()'); + +select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); +select json_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); + +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); +select json_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); + +select json_path_query('[{},1]', '$[*].keyvalue()'); +select json_path_query('{}', '$.keyvalue()'); +select json_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); +select json_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue().a'); +select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue()'; +select json '{"a": 1, "b": [1, 2]}' @? 'lax $.keyvalue().key'; + +select json_path_query('null', '$.double()'); +select json_path_query('true', '$.double()'); +select json_path_query('[]', '$.double()'); +select json_path_query('[]', 'strict $.double()'); +select json_path_query('{}', '$.double()'); +select json_path_query('1.23', '$.double()'); +select json_path_query('"1.23"', '$.double()'); +select json_path_query('"1.23aaa"', '$.double()'); +select json_path_query('"nan"', '$.double()'); +select json_path_query('"NaN"', '$.double()'); +select json_path_query('"inf"', '$.double()'); +select json_path_query('"-inf"', '$.double()'); + +select json_path_query('{}', '$.abs()'); +select json_path_query('true', '$.floor()'); +select json_path_query('"1.2"', '$.ceiling()'); + +select json_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); +select json_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); +select json_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); +select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); +select json_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); +select json_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); +select json_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); +select json_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); + +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^a b.* c " flag "ix")'); +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "m")'); +select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "s")'); + +select json_path_query('null', '$.datetime()'); +select json_path_query('true', '$.datetime()'); +select json_path_query('1', '$.datetime()'); +select json_path_query('[]', '$.datetime()'); +select json_path_query('[]', 'strict $.datetime()'); +select json_path_query('{}', '$.datetime()'); +select json_path_query('""', '$.datetime()'); +select json_path_query('"12:34"', '$.datetime("aaa")'); +select json_path_query('"12:34"', '$.datetime("aaa", 1)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 1)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 10000000000000)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483647)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", 2147483648)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); +select json_path_query('"aaaa"', '$.datetime("HH24")'); + +select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; +select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); +select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + +select json_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); +select json_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()'); +select json_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + +set time zone '+00'; + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", 12 * 60)'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", -(5 * 3600 + 12 * 60 + 34))'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + json_build_object('tz', extract(timezone from now()))); +select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")'); +select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone '+10'; + +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'); +select json_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", $tz)', + json_build_object('tz', extract(timezone from now()))); +select json_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select json_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select json_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")'); +select json_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select json_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select json_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone default; + +select json_path_query('"2017-03-10"', '$.datetime().type()'); +select json_path_query('"2017-03-10"', '$.datetime()'); +select json_path_query('"2017-03-10 12:34:56"', '$.datetime().type()'); +select json_path_query('"2017-03-10 12:34:56"', '$.datetime()'); +select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()'); +select json_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()'); +select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); +select json_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()'); +select json_path_query('"12:34:56"', '$.datetime().type()'); +select json_path_query('"12:34:56"', '$.datetime()'); +select json_path_query('"12:34:56 +3"', '$.datetime().type()'); +select json_path_query('"12:34:56 +3"', '$.datetime()'); +select json_path_query('"12:34:56 +3:10"', '$.datetime().type()'); +select json_path_query('"12:34:56 +3:10"', '$.datetime()'); + +set time zone '+00'; + +-- date comparison +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); +select json_path_query( + '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', + '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); + +-- time comparison +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); +select json_path_query( + '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', + '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); + +-- timetz comparison +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))'); +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))'); +select json_path_query( + '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', + '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))'); + +-- timestamp comparison +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); +select json_path_query( + '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); + +-- timestamptz comparison +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); +select json_path_query( + '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', + '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'); + +set time zone default; + +-- jsonpath operators + +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); + +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}'); + +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a'); +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); +SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}'); +SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}'); + +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; +SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}'); +SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}'); + +SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; +SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; From e8c6e31d706676364c5f98bec98a33a272bddf95 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 1 Feb 2019 16:57:06 +0300 Subject: [PATCH 16/66] New jsonpath support --- src/backend/utils/adt/Makefile | 4 +- src/backend/utils/adt/json.c | 2 +- src/backend/utils/adt/jsonpath_exec.c | 456 ++++++++++++++------ src/backend/utils/adt/jsonpath_json.c | 26 -- src/include/utils/jsonapi.h | 8 + src/include/utils/jsonpath_json.h | 106 ----- src/test/regress/expected/json_jsonpath.out | 84 ++-- 7 files changed, 358 insertions(+), 328 deletions(-) delete mode 100644 src/backend/utils/adt/jsonpath_json.c delete mode 100644 src/include/utils/jsonpath_json.h diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 8d81cc0d8b..580043233b 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,7 +17,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ float.o format_type.o formatting.o genfile.o \ geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \ int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \ - jsonfuncs.o jsonpath_gram.o jsonpath.o jsonpath_exec.o jsonpath_json.o \ + jsonfuncs.o jsonpath_gram.o jsonpath.o jsonpath_exec.o \ like.o like_support.o lockfuncs.o mac.o mac8.o misc.o name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ @@ -39,8 +39,6 @@ jsonpath_scan.c: FLEX_NO_BACKUP=yes # jsonpath_scan is compiled as part of jsonpath_gram jsonpath_gram.o: jsonpath_scan.c -jsonpath_json.o: jsonpath_exec.c - # jsonpath_gram.c and jsonpath_scan.c are in the distribution tarball, # so they are not cleaned here. clean distclean maintainer-clean: diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index fddcf443b1..b72738e624 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -3376,7 +3376,7 @@ JsonUnquote(Json *jb) return pnstrdup(v.val.string.val, v.val.string.len); } - return pnstrdup(jb->root.data, jb->root.len); + return JsonToCString(NULL, &jb->root, 0); } /* diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index f0689cca1a..0c403ad32b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -78,11 +78,6 @@ #include "utils/timestamp.h" #include "utils/varlena.h" -#ifdef JSONPATH_JSON_C -#define JSONXOID JSONOID -#else -#define JSONXOID JSONBOID -#endif typedef enum JsonItemType { @@ -125,12 +120,35 @@ typedef union JsonItem #define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) +typedef union Jsonx +{ + Jsonb jb; + Json js; +} Jsonx; + +#define DatumGetJsonxP(datum, isJsonb) \ + ((isJsonb) ? (Jsonx *) DatumGetJsonbP(datum) : (Jsonx *) DatumGetJsonP(datum)) + +typedef JsonbContainer JsonxContainer; + +typedef struct JsonxIterator +{ + bool isJsonb; + union + { + JsonbIterator *jb; + JsonIterator *js; + } it; +} JsonxIterator; + +#define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) + /* * Represents "base object" and it's "id" for .keyvalue() evaluation. */ typedef struct JsonBaseObjectInfo { - JsonbContainer *jbc; + JsonxContainer *jbc; int id; } JsonBaseObjectInfo; @@ -147,7 +165,8 @@ typedef struct JsonItemStackEntry typedef JsonItemStackEntry *JsonItemStack; -typedef int (*JsonPathVarCallback) (void *vars, char *varName, int varNameLen, +typedef int (*JsonPathVarCallback) (void *vars, bool isJsonb, + char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); /* @@ -222,9 +241,10 @@ typedef struct JsonValueListIterator typedef struct JsonPathUserFuncContext { FunctionCallInfo fcinfo; - Jsonb *jb; /* first jsonb function argument */ + void *js; /* first jsonb function argument */ + Json *json; JsonPath *jp; /* second jsonpath function argument */ - Jsonb *vars; /* third vars function argument */ + void *vars; /* third vars function argument */ JsonValueList found; /* resulting item list */ bool silent; /* error suppression flag */ } JsonPathUserFuncContext; @@ -251,13 +271,18 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, void *param); typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); +typedef JsonbValue *(*JsonBuilderFunc) (JsonbParseState **, + JsonbIteratorToken, + JsonbValue *); + static void freeUserFuncContext(JsonPathUserFuncContext *cxt); static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, - JsonPathUserFuncContext *cxt, bool copy); + JsonPathUserFuncContext *cxt, bool isJsonb, bool copy); static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, - Jsonb *json, bool throwErrors, + Jsonx *json, bool isJsonb, + bool throwErrors, JsonValueList *result); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, @@ -325,11 +350,11 @@ static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonItem *value); static void getJsonPathVariable(JsonPathExecContext *cxt, - JsonPathItem *variable, JsonItem *value); -static int getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, - int varNameLen, JsonItem *val, - JsonbValue *baseObject); -static int JsonbArraySize(JsonItem *jb); + JsonPathItem *variable, JsonItem *value); +static int getJsonPathVariableFromJsonx(void *varsJsonb, bool isJsonb, + char *varName, int varNameLen, + JsonItem *val, JsonbValue *baseObject); +static int JsonxArraySize(JsonItem *jb, bool isJsonb); static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p); static JsonPathBool compareItems(int32 op, JsonItem *jb1, JsonItem *jb2); @@ -354,13 +379,24 @@ static JsonItem *JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it); static int JsonbType(JsonItem *jb); static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); +static inline JsonbValue *JsonInitBinary(JsonbValue *jbv, Json *js); static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); -static JsonbValue *wrapItemsInArray(const JsonValueList *items); -static text *JsonItemUnquoteText(JsonItem *jsi); +static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); +static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb); -static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index); +static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb); + +static void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, + bool isJsonb); +static JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, + bool skipNested); +static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); +static Json *JsonItemToJson(JsonItem *jsi); +static Jsonx *JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb); +static Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb); +static Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb); static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, @@ -375,6 +411,12 @@ static void popJsonItem(JsonItemStack *stack); /****************** User interface to JsonPath executor ********************/ +#define DefineJsonPathFunc(funcname) \ +static Datum jsonx_ ## funcname(FunctionCallInfo fcinfo, bool isJsonb); \ +Datum jsonb_ ## funcname(PG_FUNCTION_ARGS) { return jsonx_ ## funcname(fcinfo, true); } \ +Datum json_ ## funcname(PG_FUNCTION_ARGS) { return jsonx_ ## funcname(fcinfo, false); } \ +static Datum jsonx_ ## funcname(FunctionCallInfo fcinfo, bool isJsonb) + /* * jsonb_path_exists * Returns true if jsonpath returns at least one item for the specified @@ -387,10 +429,9 @@ static void popJsonItem(JsonItemStack *stack); * SQL/JSON. Regarding jsonb_path_match(), this function doesn't have * an analogy in SQL/JSON, so we define its behavior on our own. */ -Datum -jsonb_path_exists(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_exists) { - JsonPathExecResult res = executeUserFunc(fcinfo, NULL, false); + JsonPathExecResult res = executeUserFunc(fcinfo, NULL, isJsonb, false); if (jperIsError(res)) PG_RETURN_NULL(); @@ -403,11 +444,10 @@ jsonb_path_exists(PG_FUNCTION_ARGS) * Implementation of operator "jsonb @? jsonpath" (2-argument version of * jsonb_path_exists()). */ -Datum -jsonb_path_exists_opr(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_exists_opr) { /* just call the other one -- it can handle both cases */ - return jsonb_path_exists(fcinfo); + return jsonx_path_exists(fcinfo, isJsonb); } /* @@ -415,12 +455,11 @@ jsonb_path_exists_opr(PG_FUNCTION_ARGS) * Returns jsonpath predicate result item for the specified jsonb value. * See jsonb_path_exists() comment for details regarding error handling. */ -Datum -jsonb_path_match(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_match) { JsonPathUserFuncContext cxt; - (void) executeUserFunc(fcinfo, &cxt, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); freeUserFuncContext(&cxt); @@ -448,11 +487,10 @@ jsonb_path_match(PG_FUNCTION_ARGS) * Implementation of operator "jsonb @@ jsonpath" (2-argument version of * jsonb_path_match()). */ -Datum -jsonb_path_match_opr(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_match_opr) { /* just call the other one -- it can handle both cases */ - return jsonb_path_match(fcinfo); + return jsonx_path_match(fcinfo, isJsonb); } /* @@ -460,13 +498,13 @@ jsonb_path_match_opr(PG_FUNCTION_ARGS) * Executes jsonpath for given jsonb document and returns result as * rowset. */ -Datum -jsonb_path_query(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_query) { FuncCallContext *funcctx; List *found; JsonItem *v; ListCell *c; + Datum res; if (SRF_IS_FIRSTCALL()) { @@ -477,7 +515,7 @@ jsonb_path_query(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* jsonb and jsonpath arguments are copied into SRF context. */ - (void) executeUserFunc(fcinfo, &jspcxt, true); + (void) executeUserFunc(fcinfo, &jspcxt, isJsonb, true); /* * Don't free jspcxt because items in jspcxt.found can reference @@ -499,7 +537,11 @@ jsonb_path_query(PG_FUNCTION_ARGS) v = lfirst(c); funcctx->user_fctx = list_delete_first(found); - SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonItemToJsonb(v))); + res = isJsonb ? + JsonbPGetDatum(JsonItemToJsonb(v)) : + JsonPGetDatum(JsonItemToJson(v)); + + SRF_RETURN_NEXT(funcctx, res); } /* @@ -507,19 +549,18 @@ jsonb_path_query(PG_FUNCTION_ARGS) * Executes jsonpath for given jsonb document and returns result as * jsonb array. */ -Datum -jsonb_path_query_array(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_query_array) { JsonPathUserFuncContext cxt; - Jsonb *jb; + Datum res; - (void) executeUserFunc(fcinfo, &cxt, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); - jb = JsonbValueToJsonb(wrapItemsInArray(&cxt.found)); + res = JsonbValueToJsonxDatum(wrapItemsInArray(&cxt.found, isJsonb), isJsonb); freeUserFuncContext(&cxt); - PG_RETURN_JSONB_P(jb); + PG_RETURN_DATUM(res); } /* @@ -527,23 +568,22 @@ jsonb_path_query_array(PG_FUNCTION_ARGS) * Executes jsonpath for given jsonb document and returns first result * item. If there are no items, NULL returned. */ -Datum -jsonb_path_query_first(PG_FUNCTION_ARGS) +DefineJsonPathFunc(path_query_first) { JsonPathUserFuncContext cxt; - Jsonb *jb; + Datum res; - (void) executeUserFunc(fcinfo, &cxt, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); if (JsonValueListLength(&cxt.found) >= 1) - jb = JsonItemToJsonb(JsonValueListHead(&cxt.found)); + res = JsonItemToJsonxDatum(JsonValueListHead(&cxt.found), isJsonb); else - jb = NULL; + res = (Datum) 0; freeUserFuncContext(&cxt); - if (jb) - PG_RETURN_JSONB_P(jb); + if (res) + PG_RETURN_DATUM(res); else PG_RETURN_NULL(); } @@ -553,16 +593,15 @@ jsonb_path_query_first(PG_FUNCTION_ARGS) * Executes jsonpath for given jsonb document and returns first result * item as text. If there are no items, NULL returned. */ -Datum -jsonb_path_query_first_text(FunctionCallInfo fcinfo) +DefineJsonPathFunc(path_query_first_text) { JsonPathUserFuncContext cxt; text *txt; - (void) executeUserFunc(fcinfo, &cxt, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); if (JsonValueListLength(&cxt.found) >= 1) - txt = JsonItemUnquoteText(JsonValueListHead(&cxt.found)); + txt = JsonItemUnquoteText(JsonValueListHead(&cxt.found), isJsonb); else txt = NULL; @@ -580,10 +619,12 @@ freeUserFuncContext(JsonPathUserFuncContext *cxt) { FunctionCallInfo fcinfo = cxt->fcinfo; - PG_FREE_IF_COPY(cxt->jb, 0); + PG_FREE_IF_COPY(cxt->js, 0); PG_FREE_IF_COPY(cxt->jp, 1); if (cxt->vars) PG_FREE_IF_COPY(cxt->vars, 2); + if (cxt->json) + pfree(cxt->json); } /* @@ -595,39 +636,60 @@ freeUserFuncContext(JsonPathUserFuncContext *cxt) */ static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, - bool copy) + bool isJsonb, bool copy) { - Jsonb *jb = copy ? PG_GETARG_JSONB_P_COPY(0) : PG_GETARG_JSONB_P(0); + Datum js_toasted = PG_GETARG_DATUM(0); + struct varlena *js_detoasted = copy ? + PG_DETOAST_DATUM(js_toasted) : + PG_DETOAST_DATUM_COPY(js_toasted); + Jsonx *js = DatumGetJsonxP(js_detoasted, isJsonb); JsonPath *jp = copy ? PG_GETARG_JSONPATH_P_COPY(1) : PG_GETARG_JSONPATH_P(1); - Jsonb *vars = NULL; + struct varlena *vars_detoasted = NULL; + Jsonx *vars = NULL; bool silent = true; JsonPathExecResult res; if (PG_NARGS() == 4) { - vars = copy ? PG_GETARG_JSONB_P_COPY(2) : PG_GETARG_JSONB_P(2); + Datum vars_toasted = PG_GETARG_DATUM(2); + + vars_detoasted = copy ? + PG_DETOAST_DATUM(vars_toasted) : + PG_DETOAST_DATUM_COPY(vars_toasted); + + vars = DatumGetJsonxP(vars_detoasted, isJsonb); + silent = PG_GETARG_BOOL(3); } if (cxt) { cxt->fcinfo = fcinfo; - cxt->jb = jb; + cxt->js = js_detoasted; cxt->jp = jp; - cxt->vars = vars; + cxt->vars = vars_detoasted; + cxt->json = isJsonb ? NULL : &js->js; cxt->silent = silent; memset(&cxt->found, 0, sizeof(cxt->found)); } - res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb, jb, - !silent, cxt ? &cxt->found : NULL); + res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonx, + js, isJsonb, !silent, cxt ? &cxt->found : NULL); if (!cxt && !copy) { - PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(js_detoasted, 0); PG_FREE_IF_COPY(jp, 1); - if (vars) - PG_FREE_IF_COPY(vars, 2); + + if (vars_detoasted) + PG_FREE_IF_COPY(vars_detoasted, 2); + + if (!isJsonb) + { + pfree(js); + if (vars) + pfree(vars); + } } return res; @@ -656,7 +718,8 @@ executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, */ static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, - Jsonb *json, bool throwErrors, JsonValueList *result) + Jsonx *json, bool isJsonb, bool throwErrors, + JsonValueList *result) { JsonPathExecContext cxt; JsonPathExecResult res; @@ -666,8 +729,16 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, jspInit(&jsp, path); - if (!JsonbExtractScalar(&json->root, &jsi.jbv)) - JsonbInitBinary(&jsi.jbv, json); + if (isJsonb) + { + if (!JsonbExtractScalar(&json->jb.root, &jsi.jbv)) + JsonbInitBinary(&jsi.jbv, &json->jb); + } + else + { + if (!JsonExtractScalar(&json->js.root, &jsi.jbv)) + JsonInitBinary(&jsi.jbv, &json->js); + } cxt.vars = vars; cxt.getVar = getVar; @@ -678,10 +749,10 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; /* 1 + number of base objects in vars */ - cxt.lastGeneratedObjectId = 1 + getVar(vars, NULL, 0, NULL, NULL); + cxt.lastGeneratedObjectId = 1 + getVar(vars, isJsonb, NULL, 0, NULL, NULL); cxt.innermostArraySize = -1; cxt.throwErrors = throwErrors; - cxt.isJsonb = true; + cxt.isJsonb = isJsonb; pushJsonItem(&cxt.stack, &root, cxt.root); @@ -830,7 +901,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { int innermostArraySize = cxt->innermostArraySize; int i; - int size = JsonbArraySize(jb); + int size = JsonxArraySize(jb, cxt->isJsonb); bool singleton = size < 0; bool hasNext = jspGetNext(jsp, &elem); @@ -892,7 +963,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } else { - jsi = getJsonArrayElement(jb, (uint32) index); + jsi = getJsonArrayElement(jb, (uint32) index, + cxt->isJsonb); if (jsi == NULL) continue; @@ -1098,7 +1170,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiSize: { - int size = JsonbArraySize(jb); + int size = JsonxArraySize(jb, cxt->isJsonb); if (size < 0) { @@ -1658,7 +1730,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, bool ignoreStructuralErrors, bool unwrapNext) { JsonPathExecResult res = jperNotFound; - JsonbIterator *it; + JsonxIterator it; int32 r; JsonItem v; @@ -1667,16 +1739,17 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, if (level > last) return res; - it = JsonbIteratorInit(jbc); + + JsonxIteratorInit(&it, jbc, cxt->isJsonb); /* * Recursively iterate over jsonb objects/arrays */ - while ((r = JsonbIteratorNext(&it, &v.jbv, true)) != WJB_DONE) + while ((r = JsonxIteratorNext(&it, &v.jbv, true)) != WJB_DONE) { if (r == WJB_KEY) { - r = JsonbIteratorNext(&it, &v.jbv, true); + r = JsonxIteratorNext(&it, &v.jbv, true); Assert(r == WJB_VALUE); } @@ -2099,8 +2172,9 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue keystr; JsonbValue valstr; JsonbValue idstr; - JsonbIterator *it; + JsonxIterator it; JsonbIteratorToken tok; + JsonBuilderFunc push; int64 id; bool hasNext; @@ -2130,28 +2204,29 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, idstr.val.string.len = 2; /* construct object id from its base object and offset inside that */ - id = jb->type != jbvBinary ? 0 : -#ifdef JSONPATH_JSON_C - (int64) ((char *) ((JsonContainer *) jbc)->data - - (char *) cxt->baseObject.jbc->data); -#else - (int64) ((char *) jbc - (char *) cxt->baseObject.jbc); -#endif + id = cxt->isJsonb ? + (int64) ((char *)(JsonContainer *) jbc - + (char *)(JsonContainer *) cxt->baseObject.jbc) : + (int64) (((JsonContainer *) jbc)->data - + ((JsonContainer *) cxt->baseObject.jbc)->data); + id += (int64) cxt->baseObject.id * INT64CONST(10000000000); idval.type = jbvNumeric; idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, Int64GetDatum(id))); - it = JsonbIteratorInit(jbc); + push = cxt->isJsonb ? pushJsonbValue : pushJsonValue; - while ((tok = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) + JsonxIteratorInit(&it, jbc, cxt->isJsonb); + + while ((tok = JsonxIteratorNext(&it, &key, true)) != WJB_DONE) { JsonBaseObjectInfo baseObject; JsonItem obj; JsonbParseState *ps; JsonbValue *keyval; - Jsonb *jsonb; + Jsonx *jsonx; if (tok != WJB_KEY) continue; @@ -2161,26 +2236,29 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!hasNext && !found) break; - tok = JsonbIteratorNext(&it, &val, true); + tok = JsonxIteratorNext(&it, &val, true); Assert(tok == WJB_VALUE); ps = NULL; - pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL); + push(&ps, WJB_BEGIN_OBJECT, NULL); pushJsonbValue(&ps, WJB_KEY, &keystr); pushJsonbValue(&ps, WJB_VALUE, &key); pushJsonbValue(&ps, WJB_KEY, &valstr); - pushJsonbValue(&ps, WJB_VALUE, &val); + push(&ps, WJB_VALUE, &val); pushJsonbValue(&ps, WJB_KEY, &idstr); pushJsonbValue(&ps, WJB_VALUE, &idval); keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); - jsonb = JsonbValueToJsonb(keyval); + jsonx = JsonbValueToJsonx(keyval, cxt->isJsonb); - JsonbInitBinary(&obj.jbv, jsonb); + if (cxt->isJsonb) + JsonbInitBinary(&obj.jbv, &jsonx->jb); + else + JsonInitBinary(&obj.jbv, &jsonx->js); baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++); @@ -2276,8 +2354,8 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, varName = jspGetString(variable, &varNameLength); if (!cxt->vars || - (baseObjectId = cxt->getVar(cxt->vars, varName, varNameLength, value, - &baseObject.jbv)) < 0) + (baseObjectId = cxt->getVar(cxt->vars, cxt->isJsonb, varName, + varNameLength, value, &baseObject.jbv)) < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find jsonpath variable \"%s\"", @@ -2288,39 +2366,61 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, } static int -getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, +getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, + char *varName, int varNameLength, JsonItem *value, JsonbValue *baseObject) { - Jsonb *vars = varsJsonb; - JsonbValue tmp; - JsonbValue *v; + Jsonx *vars = varsJsonx; + JsonbValue *val; + JsonbValue key; if (!varName) { - if (vars && !JsonContainerIsObject(&vars->root)) - { + if (vars && + !(isJsonb ? + JsonContainerIsObject(&vars->jb.root) : + JsonContainerIsObject(&vars->js.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 */ } - tmp.type = jbvString; - tmp.val.string.val = varName; - tmp.val.string.len = varNameLength; + if (!vars) + return -1; - v = findJsonbValueFromContainer(&vars->root, JB_FOBJECT, &tmp); + key.type = jbvString; + key.val.string.val = varName; + key.val.string.len = varNameLength; - if (!v) - return -1; + if (isJsonb) + { + Jsonb *jb = &vars->jb; + + val = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); + + if (!val) + return -1; + + JsonbInitBinary(baseObject, jb); + } + else + { + Json *js = &vars->js; + + val = findJsonValueFromContainer(&js->root, JB_FOBJECT, &key); - value->jbv = *v; - pfree(v); + if (!val) + return -1; + + JsonInitBinary(baseObject, js); + } + + value->jbv = *val; + pfree(val); - JsonbInitBinary(baseObject, vars); return 1; } @@ -2330,16 +2430,26 @@ getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength, * Returns the size of an array item, or -1 if item is not an array. */ static int -JsonbArraySize(JsonItem *jb) +JsonxArraySize(JsonItem *jb, bool isJsonb) { Assert(jb->type != jbvArray); if (jb->type == jbvBinary) { - JsonbContainer *jbc = (void *) jb->jbv.val.binary.data; + if (isJsonb) + { + JsonbContainer *jbc = jb->jbv.val.binary.data; + + if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) + return JsonContainerSize(jbc); + } + else + { + JsonContainer *jc = (JsonContainer *) jb->jbv.val.binary.data; - if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) - return JsonContainerSize(jbc); + if (JsonContainerIsArray(jc) && !JsonContainerIsScalar(jc)) + return JsonTextContainerSize(jc); + } } return -1; @@ -2662,7 +2772,6 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) return result; } -#ifndef JSONPATH_JSON_C /* * Initialize a binary JsonbValue with the given jsonb container. */ @@ -2675,7 +2784,19 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } -#endif + +/* + * Initialize a binary JsonbValue with the given json container. + */ +static inline JsonbValue * +JsonInitBinary(JsonbValue *jbv, Json *js) +{ + jbv->type = jbvBinary; + jbv->val.binary.data = (void *) &js->root; + jbv->val.binary.len = js->root.len; + + return jbv; +} /* * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is. @@ -2707,7 +2828,7 @@ JsonbType(JsonItem *jb) * Convert jsonb to a C-string stripping quotes from scalar strings. */ static char * -JsonbValueUnquote(JsonbValue *jbv, int *len) +JsonbValueUnquote(JsonbValue *jbv, int *len, bool isJsonb) { switch (jbv->type) { @@ -2732,12 +2853,15 @@ JsonbValueUnquote(JsonbValue *jbv, int *len) { JsonbValue jbvbuf; - if (JsonbExtractScalar(jbv->val.binary.data, &jbvbuf)) - return JsonbValueUnquote(&jbvbuf, len); + if (isJsonb ? + JsonbExtractScalar(jbv->val.binary.data, &jbvbuf) : + JsonExtractScalar((JsonContainer *) jbv->val.binary.data, &jbvbuf)) + return JsonbValueUnquote(&jbvbuf, len, isJsonb); *len = -1; - return JsonbToCString(NULL, jbv->val.binary.data, - jbv->val.binary.len); + return isJsonb ? + JsonbToCString(NULL, jbv->val.binary.data, jbv->val.binary.len) : + JsonToCString(NULL, (JsonContainer *) jbv->val.binary.data, jbv->val.binary.len); } default: @@ -2747,7 +2871,7 @@ JsonbValueUnquote(JsonbValue *jbv, int *len) } static char * -JsonItemUnquote(JsonItem *jsi, int *len) +JsonItemUnquote(JsonItem *jsi, int *len, bool isJsonb) { switch (jsi->type) { @@ -2759,15 +2883,15 @@ JsonItemUnquote(JsonItem *jsi, int *len) &jsi->datetime.tz); default: - return JsonbValueUnquote(&jsi->jbv, len); + return JsonbValueUnquote(&jsi->jbv, len, isJsonb); } } static text * -JsonItemUnquoteText(JsonItem *jsi) +JsonItemUnquoteText(JsonItem *jsi, bool isJsonb) { int len; - char *str = JsonItemUnquote(jsi, &len); + char *str = JsonItemUnquote(jsi, &len, isJsonb); if (len < 0) return cstring_to_text(str); @@ -2776,8 +2900,9 @@ JsonItemUnquoteText(JsonItem *jsi) } static JsonItem * -getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb) +getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb) { + JsonbContainer *jbc = jsi->jbv.val.binary.data; JsonbValue *val; JsonbValue key; @@ -2786,21 +2911,73 @@ getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb) key.val.string.len = keylen; val = isJsonb ? - findJsonbValueFromContainer(jb->jbv.val.binary.data, JB_FOBJECT, &key) : - getIthJsonbValueFromContainer(jb->jbv.val.binary.data, 0); + findJsonbValueFromContainer(jbc, JB_FOBJECT, &key) : + findJsonValueFromContainer((JsonContainer *) jbc, JB_FOBJECT, &key); return val ? JsonbValueToJsonItem(val) : NULL; } static JsonItem * -getJsonArrayElement(JsonItem *jb, uint32 index) +getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb) { - JsonbValue *elem = - getIthJsonbValueFromContainer(jb->jbv.val.binary.data, index); + JsonbContainer *jbc = jb->jbv.val.binary.data; + JsonbValue *elem = isJsonb ? + getIthJsonbValueFromContainer(jbc, index) : + getIthJsonValueFromContainer((JsonContainer *) jbc, index); return elem ? JsonbValueToJsonItem(elem) : NULL; } +static inline void +JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb) +{ + it->isJsonb = isJsonb; + if (isJsonb) + it->it.jb = JsonbIteratorInit((JsonbContainer *) jxc); + else + it->it.js = JsonIteratorInit((JsonContainer *) jxc); +} + +static JsonbIteratorToken +JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested) +{ + return it->isJsonb ? + JsonbIteratorNext(&it->it.jb, jbv, skipNested) : + JsonIteratorNext(&it->it.js, jbv, skipNested); +} + +static Json * +JsonItemToJson(JsonItem *jsi) +{ + JsonbValue jbv; + + return JsonbValueToJson(JsonItemToJsonbValue(jsi, &jbv)); +} + +static Jsonx * +JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb) +{ + return isJsonb ? + (Jsonx *) JsonbValueToJsonb(jbv) : + (Jsonx *) JsonbValueToJson(jbv); +} + +static Datum +JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb) +{ + return isJsonb ? + JsonbPGetDatum(JsonbValueToJsonb(jbv)) : + JsonPGetDatum(JsonbValueToJson(jbv)); +} + +static Datum +JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb) +{ + JsonbValue jbv; + + return JsonbValueToJsonxDatum(JsonItemToJsonbValue(jsi, &jbv), isJsonb); +} + /* Get scalar of given type or NULL on type mismatch */ static JsonItem * getScalar(JsonItem *scalar, enum jbvType type) @@ -2814,23 +2991,22 @@ getScalar(JsonItem *scalar, enum jbvType type) /* Construct a JSON array from the item list */ static JsonbValue * -wrapItemsInArray(const JsonValueList *items) +wrapItemsInArray(const JsonValueList *items, bool isJsonb) { JsonbParseState *ps = NULL; JsonValueListIterator it; JsonItem *jsi; + JsonbValue jbv; + JsonBuilderFunc push = isJsonb ? pushJsonbValue : pushJsonValue; - pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); + push(&ps, WJB_BEGIN_ARRAY, NULL); JsonValueListInitIterator(items, &it); - while ((jsi = JsonValueListNext(items, &it))) - { - JsonbValue jbv; - pushJsonbValue(&ps, WJB_ELEM, JsonItemToJsonbValue(jsi, &jbv)); - } + while ((jsi = JsonValueListNext(items, &it))) + push(&ps, WJB_ELEM, JsonItemToJsonbValue(jsi, &jbv)); - return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); + return push(&ps, WJB_END_ARRAY, NULL); } static void diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c deleted file mode 100644 index 498e464c8c..0000000000 --- a/src/backend/utils/adt/jsonpath_json.c +++ /dev/null @@ -1,26 +0,0 @@ -#define JSONPATH_JSON_C - -#include "postgres.h" - -#include "catalog/pg_type.h" -#include "utils/json.h" -#include "utils/jsonapi.h" -#include "utils/jsonb.h" -#include "utils/builtins.h" - -#include "utils/jsonpath_json.h" - -#define jsonb_path_exists json_path_exists -#define jsonb_path_exists_opr json_path_exists_opr -#define jsonb_path_match json_path_match -#define jsonb_path_match_opr json_path_match_opr -#define jsonb_path_query json_path_query -#define jsonb_path_query_novars json_path_query_novars -#define jsonb_path_query_array json_path_query_array -#define jsonb_path_query_array_novars json_path_query_array_novars -#define jsonb_path_query_first json_path_query_first -#define jsonb_path_query_first_novars json_path_query_first_novars -#define jsonb_path_query_first_text json_path_query_first_text -#define jsonb_path_query_first_text_novars json_path_query_first_text_novars - -#include "jsonpath_exec.c" diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index a31c2e9c22..f6ad491b11 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -116,6 +116,10 @@ typedef struct JsonContainerData typedef const JsonContainerData JsonContainer; +#define JsonTextContainerSize(jc) \ + (((jc)->header & JB_CMASK) == JB_CMASK && JsonContainerIsArray(jc) \ + ? JsonGetArraySize(jc) : (jc)->header & JB_CMASK) + typedef struct Json { JsonContainer root; @@ -136,6 +140,10 @@ typedef struct JsonIterator #define JsonPGetDatum(json) \ PointerGetDatum(cstring_to_text_with_len((json)->root.data, (json)->root.len)) +#define PG_GETARG_JSON_P(n) DatumGetJsonP(PG_GETARG_DATUM(n)) +#define PG_GETARG_JSON_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n)) +#define PG_RETURN_JSON_P(json) PG_RETURN_DATUM(JsonPGetDatum(json)) + /* * parse_json will parse the string in the lex calling the * action functions in sem at the appropriate points. It is diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h deleted file mode 100644 index 064d77ef6b..0000000000 --- a/src/include/utils/jsonpath_json.h +++ /dev/null @@ -1,106 +0,0 @@ -/*------------------------------------------------------------------------- - * - * jsonpath_json.h - * Jsonpath support for json datatype - * - * Copyright (c) 2017, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/include/utils/jsonpath_json.h - * - *------------------------------------------------------------------------- - */ - -#ifndef JSONPATH_JSON_H -#define JSONPATH_JSON_H - -/* redefine jsonb structures */ -#define Jsonb Json -#define JsonbContainer JsonContainer -#define JsonbIterator JsonIterator - -/* redefine jsonb functions */ -#define findJsonbValueFromContainer(jc, flags, jbv) \ - findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv) -#define getIthJsonbValueFromContainer(jc, i) \ - getIthJsonValueFromContainer((JsonContainer *)(jc), i) -#define pushJsonbValue pushJsonValue -#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc)) -#define JsonbIteratorNext JsonIteratorNext -#define JsonbValueToJsonb JsonbValueToJson -#define JsonbToCString JsonToCString -#define JsonbUnquote JsonUnquote -#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv) - -/* redefine jsonb macros */ -#undef JsonContainerSize -#define JsonContainerSize(jc) \ - ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \ - JsonContainerIsArray(jc) \ - ? JsonGetArraySize((JsonContainer *)(jc)) \ - : ((JsonContainer *)(jc))->header & JB_CMASK) - - -#undef DatumGetJsonbP -#define DatumGetJsonbP(d) DatumGetJsonP(d) - -#undef DatumGetJsonbPCopy -#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d) - -#undef JsonbPGetDatum -#define JsonbPGetDatum(json) JsonPGetDatum(json) - -#undef PG_GETARG_JSONB_P -#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n)) - -#undef PG_GETARG_JSONB_P_COPY -#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n)) - -#undef PG_RETURN_JSONB_P -#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json)) - - -#ifdef DatumGetJsonb -#undef DatumGetJsonb -#define DatumGetJsonb(d) DatumGetJsonbP(d) -#endif - -#ifdef DatumGetJsonbCopy -#undef DatumGetJsonbCopy -#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d) -#endif - -#ifdef JsonbGetDatum -#undef JsonbGetDatum -#define JsonbGetDatum(json) JsonbPGetDatum(json) -#endif - -#ifdef PG_GETARG_JSONB -#undef PG_GETARG_JSONB -#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n) -#endif - -#ifdef PG_GETARG_JSONB_COPY -#undef PG_GETARG_JSONB_COPY -#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n) -#endif - -#ifdef PG_RETURN_JSONB -#undef PG_RETURN_JSONB -#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json) -#endif - -/* redefine global jsonpath functions */ -#define executeJsonPath executeJsonPathJson - -static inline JsonbValue * -JsonbInitBinary(JsonbValue *jbv, Json *jb) -{ - jbv->type = jbvBinary; - jbv->val.binary.data = (void *) &jb->root; - jbv->val.binary.len = jb->root.len; - - return jbv; -} - -#endif /* JSONPATH_JSON_H */ diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index 926d835473..f3f6a0b8a7 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -1826,63 +1826,56 @@ set time zone '+00'; select json_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))'); - json_path_query ------------------------------ + json_path_query +----------------------- "2017-03-10" "2017-03-10T00:00:00" - "2017-03-10T03:00:00+03:00" -(3 rows) +(2 rows) select json_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))'); - json_path_query ------------------------------ + json_path_query +----------------------- "2017-03-10" "2017-03-11" "2017-03-10T00:00:00" "2017-03-10T12:34:56" - "2017-03-10T03:00:00+03:00" -(5 rows) +(4 rows) select json_path_query( '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]', '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))'); - json_path_query ------------------------------ + json_path_query +----------------- "2017-03-09" - "2017-03-10T01:02:03+04:00" -(2 rows) +(1 row) -- time comparison select json_path_query( '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))'); - json_path_query ------------------- + json_path_query +----------------- "12:35:00" - "12:35:00+00:00" -(2 rows) +(1 row) select json_path_query( '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))'); - json_path_query ------------------- + json_path_query +----------------- "12:35:00" "12:36:00" - "12:35:00+00:00" -(3 rows) +(2 rows) select json_path_query( '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]', '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))'); - json_path_query ------------------- + json_path_query +----------------- "12:34:00" - "12:35:00+01:00" - "13:35:00+01:00" -(3 rows) +(1 row) -- timetz comparison select json_path_query( @@ -1901,9 +1894,7 @@ select json_path_query( "12:35:00+01:00" "12:36:00+01:00" "12:35:00-02:00" - "11:35:00" - "12:35:00" -(5 rows) +(3 rows) select json_path_query( '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]', @@ -1912,40 +1903,35 @@ select json_path_query( ------------------ "12:34:00+01:00" "12:35:00+02:00" - "10:35:00" -(3 rows) +(2 rows) -- timestamp comparison select json_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - json_path_query ------------------------------ + json_path_query +----------------------- "2017-03-10T12:35:00" - "2017-03-10T13:35:00+01:00" -(2 rows) +(1 row) select json_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - json_path_query ------------------------------ + json_path_query +----------------------- "2017-03-10T12:35:00" "2017-03-10T12:36:00" - "2017-03-10T13:35:00+01:00" - "2017-03-10T12:35:00-01:00" "2017-03-11" -(5 rows) +(3 rows) select json_path_query( '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))'); - json_path_query ------------------------------ + json_path_query +----------------------- "2017-03-10T12:34:00" - "2017-03-10T12:35:00+01:00" "2017-03-10" -(3 rows) +(2 rows) -- timestamptz comparison select json_path_query( @@ -1954,8 +1940,7 @@ select json_path_query( json_path_query ----------------------------- "2017-03-10T12:35:00+01:00" - "2017-03-10T11:35:00" -(2 rows) +(1 row) select json_path_query( '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', @@ -1965,10 +1950,7 @@ select json_path_query( "2017-03-10T12:35:00+01:00" "2017-03-10T12:36:00+01:00" "2017-03-10T12:35:00-02:00" - "2017-03-10T11:35:00" - "2017-03-10T12:35:00" - "2017-03-11" -(6 rows) +(3 rows) select json_path_query( '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]', @@ -1977,9 +1959,7 @@ select json_path_query( ----------------------------- "2017-03-10T12:34:00+01:00" "2017-03-10T12:35:00+02:00" - "2017-03-10T10:35:00" - "2017-03-10" -(4 rows) +(2 rows) set time zone default; -- jsonpath operators From 4ef9ac13182c0791fd5e534e7d6914ac620ed75b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 28 Feb 2019 22:49:23 +0300 Subject: [PATCH 17/66] Refactor jsonpath function definitions --- src/backend/utils/adt/jsonpath_exec.c | 144 ++++++++++++++++++++------ 1 file changed, 115 insertions(+), 29 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0c403ad32b..6fcc11dca1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -411,14 +411,8 @@ static void popJsonItem(JsonItemStack *stack); /****************** User interface to JsonPath executor ********************/ -#define DefineJsonPathFunc(funcname) \ -static Datum jsonx_ ## funcname(FunctionCallInfo fcinfo, bool isJsonb); \ -Datum jsonb_ ## funcname(PG_FUNCTION_ARGS) { return jsonx_ ## funcname(fcinfo, true); } \ -Datum json_ ## funcname(PG_FUNCTION_ARGS) { return jsonx_ ## funcname(fcinfo, false); } \ -static Datum jsonx_ ## funcname(FunctionCallInfo fcinfo, bool isJsonb) - /* - * jsonb_path_exists + * json[b]_path_exists * Returns true if jsonpath returns at least one item for the specified * jsonb value. This function and jsonb_path_match() are used to * implement @? and @@ operators, which in turn are intended to have an @@ -429,7 +423,8 @@ static Datum jsonx_ ## funcname(FunctionCallInfo fcinfo, bool isJsonb) * SQL/JSON. Regarding jsonb_path_match(), this function doesn't have * an analogy in SQL/JSON, so we define its behavior on our own. */ -DefineJsonPathFunc(path_exists) +static Datum +jsonx_path_exists(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathExecResult res = executeUserFunc(fcinfo, NULL, isJsonb, false); @@ -439,23 +434,42 @@ DefineJsonPathFunc(path_exists) PG_RETURN_BOOL(res == jperOk); } +Datum +jsonb_path_exists(PG_FUNCTION_ARGS) +{ + return jsonx_path_exists(fcinfo, true); +} + +Datum +json_path_exists(PG_FUNCTION_ARGS) +{ + return jsonx_path_exists(fcinfo, false); +} + /* - * jsonb_path_exists_opr - * Implementation of operator "jsonb @? jsonpath" (2-argument version of - * jsonb_path_exists()). + * json[b]_path_exists_opr + * Implementation of operator "json[b] @? jsonpath" (2-argument version of + * json[b]_path_exists()). */ -DefineJsonPathFunc(path_exists_opr) +Datum +jsonb_path_exists_opr(PG_FUNCTION_ARGS) { - /* just call the other one -- it can handle both cases */ - return jsonx_path_exists(fcinfo, isJsonb); + return jsonx_path_exists(fcinfo, true); +} + +Datum +json_path_exists_opr(PG_FUNCTION_ARGS) +{ + return jsonx_path_exists(fcinfo, false); } /* - * jsonb_path_match + * json[b]_path_match * Returns jsonpath predicate result item for the specified jsonb value. * See jsonb_path_exists() comment for details regarding error handling. */ -DefineJsonPathFunc(path_match) +static Datum +jsonx_path_match(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathUserFuncContext cxt; @@ -482,23 +496,44 @@ DefineJsonPathFunc(path_match) PG_RETURN_NULL(); } +Datum +jsonb_path_match(PG_FUNCTION_ARGS) +{ + return jsonx_path_match(fcinfo, true); +} + +Datum +json_path_match(PG_FUNCTION_ARGS) +{ + return jsonx_path_match(fcinfo, false); +} + /* - * jsonb_path_match_opr - * Implementation of operator "jsonb @@ jsonpath" (2-argument version of - * jsonb_path_match()). + * json[b]_path_match_opr + * Implementation of operator "json[b] @@ jsonpath" (2-argument version of + * json[b]_path_match()). */ -DefineJsonPathFunc(path_match_opr) +Datum +jsonb_path_match_opr(PG_FUNCTION_ARGS) { /* just call the other one -- it can handle both cases */ - return jsonx_path_match(fcinfo, isJsonb); + return jsonx_path_match(fcinfo, true); +} + +Datum +json_path_match_opr(PG_FUNCTION_ARGS) +{ + /* just call the other one -- it can handle both cases */ + return jsonx_path_match(fcinfo, false); } /* - * jsonb_path_query + * json[b]_path_query * Executes jsonpath for given jsonb document and returns result as * rowset. */ -DefineJsonPathFunc(path_query) +static Datum +jsonx_path_query(PG_FUNCTION_ARGS, bool isJsonb) { FuncCallContext *funcctx; List *found; @@ -544,12 +579,25 @@ DefineJsonPathFunc(path_query) SRF_RETURN_NEXT(funcctx, res); } +Datum +jsonb_path_query(PG_FUNCTION_ARGS) +{ + return jsonx_path_query(fcinfo, true); +} + +Datum +json_path_query(PG_FUNCTION_ARGS) +{ + return jsonx_path_query(fcinfo, false); +} + /* - * jsonb_path_query_array + * json[b]_path_query_array * Executes jsonpath for given jsonb document and returns result as * jsonb array. */ -DefineJsonPathFunc(path_query_array) +static Datum +jsonx_path_query_array(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathUserFuncContext cxt; Datum res; @@ -563,12 +611,25 @@ DefineJsonPathFunc(path_query_array) PG_RETURN_DATUM(res); } +Datum +jsonb_path_query_array(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_array(fcinfo, true); +} + +Datum +json_path_query_array(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_array(fcinfo, false); +} + /* - * jsonb_path_query_first + * json[b]_path_query_first * Executes jsonpath for given jsonb document and returns first result * item. If there are no items, NULL returned. */ -DefineJsonPathFunc(path_query_first) +static Datum +jsonx_path_query_first(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathUserFuncContext cxt; Datum res; @@ -588,12 +649,25 @@ DefineJsonPathFunc(path_query_first) PG_RETURN_NULL(); } +Datum +jsonb_path_query_first(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_first(fcinfo, true); +} + +Datum +json_path_query_first(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_first(fcinfo, false); +} + /* - * jsonb_path_query_first_text + * json[b]_path_query_first_text * Executes jsonpath for given jsonb document and returns first result * item as text. If there are no items, NULL returned. */ -DefineJsonPathFunc(path_query_first_text) +static Datum +jsonx_path_query_first_text(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathUserFuncContext cxt; text *txt; @@ -613,6 +687,18 @@ DefineJsonPathFunc(path_query_first_text) PG_RETURN_NULL(); } +Datum +jsonb_path_query_first_text(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_first_text(fcinfo, true); +} + +Datum +json_path_query_first_text(PG_FUNCTION_ARGS) +{ + return jsonx_path_query_first_text(fcinfo, false); +} + /* Free untoasted copies of jsonb and jsonpath arguments. */ static void freeUserFuncContext(JsonPathUserFuncContext *cxt) From 30b8f22e0016d486ffa71e2900f9b11c36ebaf7f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 28 Feb 2019 02:57:32 +0300 Subject: [PATCH 18/66] Introduce JsonItem access macros --- src/backend/utils/adt/jsonpath_exec.c | 315 +++++++++++++++----------- 1 file changed, 187 insertions(+), 128 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 6fcc11dca1..70cf7f366e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -110,7 +110,7 @@ typedef union JsonItem struct { - enum jbvType type; + int type; Datum value; Oid typid; int32 typmod; @@ -118,6 +118,28 @@ typedef union JsonItem } datetime; } JsonItem; +#define JsonItemJbv(jsi) (&(jsi)->jbv) +#define JsonItemBool(jsi) (JsonItemJbv(jsi)->val.boolean) +#define JsonItemNumeric(jsi) (JsonItemJbv(jsi)->val.numeric) +#define JsonItemNumericDatum(jsi) NumericGetDatum(JsonItemNumeric(jsi)) +#define JsonItemString(jsi) (JsonItemJbv(jsi)->val.string) +#define JsonItemBinary(jsi) (JsonItemJbv(jsi)->val.binary) +#define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array) +#define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object) +#define JsonItemDatetime(jsi) ((jsi)->datetime) + +#define JsonItemGetType(jsi) ((jsi)->type) +#define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull) +#define JsonItemIsBool(jsi) (JsonItemGetType(jsi) == jsiBool) +#define JsonItemIsNumeric(jsi) (JsonItemGetType(jsi) == jsiNumeric) +#define JsonItemIsString(jsi) (JsonItemGetType(jsi) == jsiString) +#define JsonItemIsBinary(jsi) (JsonItemGetType(jsi) == jsiBinary) +#define JsonItemIsArray(jsi) (JsonItemGetType(jsi) == jsiArray) +#define JsonItemIsObject(jsi) (JsonItemGetType(jsi) == jsiObject) +#define JsonItemIsDatetime(jsi) (JsonItemGetType(jsi) == jsiDatetime) +#define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \ + JsonItemIsDatetime(jsi)) + #define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) typedef union Jsonx @@ -359,6 +381,16 @@ static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p); static JsonPathBool compareItems(int32 op, JsonItem *jb1, JsonItem *jb2); static int compareNumeric(Numeric a, Numeric b); + +static void JsonItemInitNull(JsonItem *item); +static void JsonItemInitBool(JsonItem *item, bool val); +static void JsonItemInitNumeric(JsonItem *item, Numeric val); +#define JsonItemInitNumericDatum(item, val) \ + JsonItemInitNumeric(item, DatumGetNumeric(val)) +static void JsonItemInitString(JsonItem *item, char *str, int len); +static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, + int32 typmod, int tz); + static JsonItem *copyJsonItem(JsonItem *src); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); static Jsonb *JsonItemToJsonb(JsonItem *jsi); @@ -479,12 +511,12 @@ jsonx_path_match(PG_FUNCTION_ARGS, bool isJsonb) if (JsonValueListLength(&cxt.found) == 1) { - JsonItem *jsi = JsonValueListHead(&cxt.found); + JsonItem *res = JsonValueListHead(&cxt.found); - if (jsi->type == jbvBool) - PG_RETURN_BOOL(jsi->jbv.val.boolean); + if (JsonItemIsBool(res)) + PG_RETURN_BOOL(JsonItemBool(res)); - if (jsi->type == jbvNull) + if (JsonItemIsNull(res)) PG_RETURN_NULL(); } @@ -811,19 +843,20 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, JsonPathExecResult res; JsonPathItem jsp; JsonItem jsi; + JsonbValue *jbv = JsonItemJbv(&jsi); JsonItemStackEntry root; jspInit(&jsp, path); if (isJsonb) { - if (!JsonbExtractScalar(&json->jb.root, &jsi.jbv)) - JsonbInitBinary(&jsi.jbv, &json->jb); + if (!JsonbExtractScalar(&json->jb.root, jbv)) + JsonbInitBinary(jbv, &json->jb); } else { - if (!JsonExtractScalar(&json->js.root, &jsi.jbv)) - JsonInitBinary(&jsi.jbv, &json->js); + if (!JsonExtractScalar(&json->js.root, jbv)) + JsonInitBinary(jbv, &json->js); } cxt.vars = vars; @@ -1108,10 +1141,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, lastjsi = hasNext ? &tmpjsi : palloc(sizeof(*lastjsi)); - lastjsi->jbv.type = jbvNumeric; - lastjsi->jbv.val.numeric = - DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(last))); + JsonItemInitNumericDatum(lastjsi, + DirectFunctionCall1(int4_numeric, + Int32GetDatum(last))); res = executeNextItem(cxt, jsp, &elem, lastjsi, found, hasNext); } @@ -1127,7 +1159,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeAnyItem (cxt, hasNext ? &elem : NULL, - jb->jbv.val.binary.data, found, 1, 1, 1, + JsonItemBinary(jb).data, found, 1, 1, 1, false, jspAutoUnwrap(cxt)); } else if (unwrap && JsonbType(jb) == jbvArray) @@ -1205,10 +1237,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } - if (jb->type == jbvBinary) + if (JsonItemIsBinary(jb)) res = executeAnyItem (cxt, hasNext ? &elem : NULL, - jb->jbv.val.binary.data, found, + JsonItemBinary(jb).data, found, 1, jsp->content.anybounds.first, jsp->content.anybounds.last, @@ -1245,10 +1277,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiType: { JsonItem jsi; + const char *typname = JsonItemTypeName(jb); - jsi.jbv.type = jbvString; - jsi.jbv.val.string.val = pstrdup(JsonItemTypeName(jb)); - jsi.jbv.val.string.len = strlen(jsi.jbv.val.string.val); + JsonItemInitString(&jsi, pstrdup(typname), strlen(typname)); res = executeNextItem(cxt, jsp, NULL, &jsi, found, true); } @@ -1275,10 +1306,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = palloc(sizeof(*jb)); - jb->jbv.type = jbvNumeric; - jb->jbv.val.numeric = - DatumGetNumeric(DirectFunctionCall1(int4_numeric, - Int32GetDatum(size))); + JsonItemInitNumericDatum(jb, DirectFunctionCall1(int4_numeric, + Int32GetDatum(size))); res = executeNextItem(cxt, jsp, NULL, jb, found, false); } @@ -1304,10 +1333,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); - if (jb->type == jbvNumeric) + if (JsonItemIsNumeric(jb)) { char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, - NumericGetDatum(jb->jbv.val.numeric))); + JsonItemNumericDatum(jb))); bool have_error = false; (void) float8in_internal_opt_error(tmp, @@ -1323,12 +1352,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jspOperationName(jsp->type))))); res = jperOk; } - else if (jb->type == jbvString) + else if (JsonItemIsString(jb)) { /* cast string as double */ double val; - char *tmp = pnstrdup(jb->jbv.val.string.val, - jb->jbv.val.string.len); + char *tmp = pnstrdup(JsonItemString(jb).val, + JsonItemString(jb).len); bool have_error = false; val = float8in_internal_opt_error(tmp, @@ -1344,9 +1373,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jspOperationName(jsp->type))))); jb = &jsi; - jb->jbv.type = jbvNumeric; - jb->jbv.val.numeric = DatumGetNumeric(DirectFunctionCall1(float8_numeric, - Float8GetDatum(val))); + JsonItemInitNumericDatum(jb, DirectFunctionCall1(float8_numeric, + Float8GetDatum(val))); res = jperOk; } @@ -1381,8 +1409,8 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to a string value", jspOperationName(jsp->type))))); - datetime = cstring_to_text_with_len(jb->jbv.val.string.val, - jb->jbv.val.string.len); + datetime = cstring_to_text_with_len(JsonItemString(jb).val, + JsonItemString(jb).len); if (jsp->content.args.left) { @@ -1409,21 +1437,21 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return tzres; if (JsonValueListLength(&tzlist) != 1 || - ((tzjsi = JsonValueListHead(&tzlist))->type != jbvString && - tzjsi->type != jbvNumeric)) + (!JsonItemIsString((tzjsi = JsonValueListHead(&tzlist))) && + !JsonItemIsNumeric(tzjsi))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), errmsg("timezone argument of jsonpath item method .%s() is not a singleton string or number", jspOperationName(jsp->type))))); - if (tzjsi->type == jbvString) - tzname = pnstrdup(tzjsi->jbv.val.string.val, - tzjsi->jbv.val.string.len); + if (JsonItemIsString(tzjsi)) + tzname = pnstrdup(JsonItemString(tzjsi).val, + JsonItemString(tzjsi).len); else { bool error = false; - tz = numeric_int4_opt_error(tzjsi->jbv.val.numeric, + tz = numeric_int4_opt_error(JsonItemNumeric(tzjsi), &error); if (error || tz == PG_INT32_MIN) @@ -1510,11 +1538,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); - jb->type = jsiDatetime; - jb->datetime.value = value; - jb->datetime.typid = typid; - jb->datetime.typmod = typmod; - jb->datetime.tz = tz; + JsonItemInitDatetime(jb, value, typid, typmod, tz); res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext); } @@ -1541,14 +1565,14 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, JsonValueList *found, bool unwrapElements) { - if (jb->type != jbvBinary) + if (!JsonItemIsBinary(jb)) { Assert(jb->type != jbvArray); elog(ERROR, "invalid jsonb array value type: %d", jb->type); } return executeAnyItem - (cxt, jsp, jb->jbv.val.binary.data, found, 1, 1, 1, + (cxt, jsp, JsonItemBinary(jb).data, found, 1, 1, 1, false, unwrapElements); } @@ -1623,7 +1647,7 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueListInitIterator(&seq, &it); while ((item = JsonValueListNext(&seq, &it))) { - Assert(item->type != jbvArray); + Assert(!JsonItemIsArray(item)); if (JsonbType(item) == jbvArray) executeItemUnwrapTargetArray(cxt, NULL, item, found, false); @@ -1831,11 +1855,11 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, /* * Recursively iterate over jsonb objects/arrays */ - while ((r = JsonxIteratorNext(&it, &v.jbv, true)) != WJB_DONE) + while ((r = JsonxIteratorNext(&it, JsonItemJbv(&v), true)) != WJB_DONE) { if (r == WJB_KEY) { - r = JsonxIteratorNext(&it, &v.jbv, true); + r = JsonxIteratorNext(&it, JsonItemJbv(&v), true); Assert(r == WJB_VALUE); } @@ -1844,7 +1868,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, if (level >= first || (first == PG_UINT32_MAX && last == PG_UINT32_MAX && - v.type != jbvBinary)) /* leaves only requested */ + !JsonItemIsBinary(&v))) /* leaves only requested */ { /* check expression */ if (jsp) @@ -1873,10 +1897,10 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, return jperOk; } - if (level < last && v.type == jbvBinary) + if (level < last && JsonItemIsBinary(&v)) { res = executeAnyItem - (cxt, jsp, v.jbv.val.binary.data, found, + (cxt, jsp, JsonItemBinary(&v).data, found, level + 1, first, last, ignoreStructuralErrors, unwrapNext); @@ -2027,13 +2051,13 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jspThrowErrors(cxt)) { - res = func(lval->jbv.val.numeric, rval->jbv.val.numeric, NULL); + res = func(JsonItemNumeric(lval), JsonItemNumeric(rval), NULL); } else { bool error = false; - res = func(lval->jbv.val.numeric, rval->jbv.val.numeric, &error); + res = func(JsonItemNumeric(lval), JsonItemNumeric(rval), &error); if (error) return jperError; @@ -2043,8 +2067,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperOk; lval = palloc(sizeof(*lval)); - lval->jbv.type = jbvNumeric; - lval->jbv.val.numeric = res; + JsonItemInitNumeric(lval, res); return executeNextItem(cxt, jsp, &elem, lval, found, false); } @@ -2095,9 +2118,9 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, } if (func) - val->jbv.val.numeric = + JsonItemNumeric(val) = DatumGetNumeric(DirectFunctionCall1(func, - NumericGetDatum(val->jbv.val.numeric))); + NumericGetDatum(JsonItemNumeric(val)))); jper2 = executeNextItem(cxt, jsp, &elem, val, found, false); @@ -2130,10 +2153,10 @@ executeStartsWith(JsonPathItem *jsp, JsonItem *whole, JsonItem *initial, if (!(initial = getScalar(initial, jbvString))) return jpbUnknown; /* error */ - if (whole->jbv.val.string.len >= initial->jbv.val.string.len && - !memcmp(whole->jbv.val.string.val, - initial->jbv.val.string.val, - initial->jbv.val.string.len)) + if (JsonItemString(whole).len >= JsonItemString(initial).len && + !memcmp(JsonItemString(whole).val, + JsonItemString(initial).val, + JsonItemString(initial).len)) return jpbTrue; return jpbFalse; @@ -2181,8 +2204,8 @@ executeLikeRegex(JsonPathItem *jsp, JsonItem *str, JsonItem *rarg, } } - if (RE_compile_and_execute(cxt->regex, str->jbv.val.string.val, - str->jbv.val.string.len, + if (RE_compile_and_execute(cxt->regex, JsonItemString(str).val, + JsonItemString(str).len, cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL)) return jpbTrue; @@ -2210,14 +2233,13 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); - datum = DirectFunctionCall1(func, NumericGetDatum(jb->jbv.val.numeric)); + datum = DirectFunctionCall1(func, JsonItemNumericDatum(jb)); if (!jspGetNext(jsp, &next) && !found) return jperOk; jb = palloc(sizeof(*jb)); - jb->jbv.type = jbvNumeric; - jb->jbv.val.numeric = DatumGetNumeric(datum); + JsonItemInitNumericDatum(jb, datum); return executeNextItem(cxt, jsp, &next, jb, found, false); } @@ -2270,7 +2292,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, errmsg("jsonpath item method .%s() can only be applied to an object", jspOperationName(jsp->type))))); - jbc = jb->jbv.val.binary.data; + jbc = JsonItemBinary(jb).data; if (!JsonContainerSize(jbc)) return jperNotFound; /* no key-value pairs */ @@ -2342,9 +2364,9 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, jsonx = JsonbValueToJsonx(keyval, cxt->isJsonb); if (cxt->isJsonb) - JsonbInitBinary(&obj.jbv, &jsonx->jb); + JsonbInitBinary(JsonItemJbv(&obj), &jsonx->jb); else - JsonInitBinary(&obj.jbv, &jsonx->js); + JsonInitBinary(JsonItemJbv(&obj), &jsonx->js); baseObject = setBaseObject(cxt, &obj, cxt->lastGeneratedObjectId++); @@ -2377,14 +2399,9 @@ appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperOk; /* found singleton boolean value */ if (res == jpbUnknown) - { - jsi.jbv.type = jbvNull; - } + JsonItemInitNull(&jsi); else - { - jsi.jbv.type = jbvBool; - jsi.jbv.val.boolean = res == jpbTrue; - } + JsonItemInitBool(&jsi, res == jpbTrue); return executeNextItem(cxt, jsp, &next, &jsi, found, true); } @@ -2401,21 +2418,22 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, switch (item->type) { case jpiNull: - value->type = jbvNull; + JsonItemInitNull(value); break; case jpiBool: - value->type = jbvBool; - value->jbv.val.boolean = jspGetBool(item); + JsonItemInitBool(value, jspGetBool(item)); break; case jpiNumeric: - value->type = jbvNumeric; - value->jbv.val.numeric = jspGetNumeric(item); + JsonItemInitNumeric(value, jspGetNumeric(item)); break; case jpiString: - value->type = jbvString; - value->jbv.val.string.val = jspGetString(item, - &value->jbv.val.string.len); - break; + { + int len; + char *str = jspGetString(item, &len); + + JsonItemInitString(value, str, len); + break; + } case jpiVariable: getJsonPathVariable(cxt, item, value); return; @@ -2440,8 +2458,9 @@ getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable, varName = jspGetString(variable, &varNameLength); if (!cxt->vars || - (baseObjectId = cxt->getVar(cxt->vars, cxt->isJsonb, varName, - varNameLength, value, &baseObject.jbv)) < 0) + (baseObjectId = cxt->getVar(cxt->vars, cxt->isJsonb, + varName, varNameLength, + value, JsonItemJbv(&baseObject))) < 0) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("could not find jsonpath variable \"%s\"", @@ -2518,20 +2537,20 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, static int JsonxArraySize(JsonItem *jb, bool isJsonb) { - Assert(jb->type != jbvArray); + Assert(!JsonItemIsArray(jb)); - if (jb->type == jbvBinary) + if (JsonItemIsBinary(jb)) { if (isJsonb) { - JsonbContainer *jbc = jb->jbv.val.binary.data; + JsonbContainer *jbc = JsonItemBinary(jb).data; if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) return JsonContainerSize(jbc); } else { - JsonContainer *jc = (JsonContainer *) jb->jbv.val.binary.data; + JsonContainer *jc = (JsonContainer *) JsonItemBinary(jb).data; if (JsonContainerIsArray(jc) && !JsonContainerIsScalar(jc)) return JsonTextContainerSize(jc); @@ -2554,14 +2573,14 @@ executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p) static JsonPathBool compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) { - JsonbValue *jb1 = &jsi1->jbv; - JsonbValue *jb2 = &jsi2->jbv; + JsonbValue *jb1 = JsonItemJbv(jsi1); + JsonbValue *jb2 = JsonItemJbv(jsi2); int cmp; bool res; - if (jb1->type != jb2->type) + if (JsonItemGetType(jsi1) != JsonItemGetType(jsi2)) { - if (jb1->type == jbvNull || jb2->type == jbvNull) + if (JsonItemIsNull(jsi1) || JsonItemIsNull(jsi2)) /* * Equality and order comparison of nulls to non-nulls returns @@ -2573,7 +2592,7 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) return jpbUnknown; } - switch (jsi1->type) + switch (JsonItemGetType(jsi1)) { case jbvNull: cmp = 0; @@ -2600,12 +2619,12 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) { bool error = false; - cmp = compareDatetime(jsi1->datetime.value, - jsi1->datetime.typid, - jsi1->datetime.tz, - jsi2->datetime.value, - jsi2->datetime.typid, - jsi2->datetime.tz, + cmp = compareDatetime(JsonItemDatetime(jsi1).value, + JsonItemDatetime(jsi1).typid, + JsonItemDatetime(jsi1).tz, + JsonItemDatetime(jsi2).value, + JsonItemDatetime(jsi2).typid, + JsonItemDatetime(jsi2).tz, &error); if (error) @@ -2619,7 +2638,7 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) return jpbUnknown; /* non-scalars are not comparable */ default: - elog(ERROR, "invalid jsonb value type %d", jb1->type); + elog(ERROR, "invalid jsonb value type %d", JsonItemGetType(jsi1)); } switch (op) @@ -2672,19 +2691,19 @@ copyJsonItem(JsonItem *src) static JsonbValue * JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) { - switch (jsi->type) + switch (JsonItemGetType(jsi)) { case jsiDatetime: jbv->type = jbvString; jbv->val.string.val = JsonEncodeDateTime(NULL, - jsi->datetime.value, - jsi->datetime.typid, - &jsi->datetime.tz); + JsonItemDatetime(jsi).value, + JsonItemDatetime(jsi).typid, + &JsonItemDatetime(jsi).tz); jbv->val.string.len = strlen(jbv->val.string.val); return jbv; default: - return &jsi->jbv; + return JsonItemJbv(jsi); } } @@ -2699,10 +2718,10 @@ JsonItemToJsonb(JsonItem *jsi) static const char * JsonItemTypeName(JsonItem *jsi) { - switch (jsi->type) + switch (JsonItemGetType(jsi)) { case jsiDatetime: - switch (jsi->datetime.typid) + switch (JsonItemDatetime(jsi).typid) { case DATEOID: return "date"; @@ -2716,12 +2735,12 @@ JsonItemTypeName(JsonItem *jsi) return "timestamp with time zone"; default: elog(ERROR, "unrecognized jsonb value datetime type: %d", - jsi->datetime.typid); + JsonItemDatetime(jsi).typid); return "unknown"; } default: - return JsonbTypeName(&jsi->jbv); + return JsonbTypeName(JsonItemJbv(jsi)); } } @@ -2749,7 +2768,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, errmsg("jsonpath array subscript is not a single numeric value")))); numeric_index = DirectFunctionCall2(numeric_trunc, - NumericGetDatum(jbv->jbv.val.numeric), + NumericGetDatum(JsonItemNumeric(jbv)), Int32GetDatum(0)); *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index), @@ -2769,8 +2788,8 @@ setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id) { JsonBaseObjectInfo baseObject = cxt->baseObject; - cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL : - (JsonbContainer *) jbv->jbv.val.binary.data; + cxt->baseObject.jbc = !JsonItemIsBinary(jbv) ? NULL : + (JsonbContainer *) JsonItemBinary(jbv).data; cxt->baseObject.id = id; return baseObject; @@ -2890,11 +2909,11 @@ JsonInitBinary(JsonbValue *jbv, Json *js) static int JsonbType(JsonItem *jb) { - int type = jb->type; + int type = JsonItemGetType(jb); - if (jb->type == jbvBinary) + if (type == jbvBinary) { - JsonbContainer *jbc = (void *) jb->jbv.val.binary.data; + JsonbContainer *jbc = (void *) JsonItemBinary(jb).data; /* Scalars should be always extracted during jsonpath execution. */ Assert(!JsonContainerIsScalar(jbc)); @@ -2959,17 +2978,17 @@ JsonbValueUnquote(JsonbValue *jbv, int *len, bool isJsonb) static char * JsonItemUnquote(JsonItem *jsi, int *len, bool isJsonb) { - switch (jsi->type) + switch (JsonItemGetType(jsi)) { case jsiDatetime: *len = -1; return JsonEncodeDateTime(NULL, - jsi->datetime.value, - jsi->datetime.typid, - &jsi->datetime.tz); + JsonItemDatetime(jsi).value, + JsonItemDatetime(jsi).typid, + &JsonItemDatetime(jsi).tz); default: - return JsonbValueUnquote(&jsi->jbv, len, isJsonb); + return JsonbValueUnquote(JsonItemJbv(jsi), len, isJsonb); } } @@ -3006,7 +3025,7 @@ getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb) static JsonItem * getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb) { - JsonbContainer *jbc = jb->jbv.val.binary.data; + JsonbContainer *jbc = JsonItemBinary(jb).data; JsonbValue *elem = isJsonb ? getIthJsonbValueFromContainer(jbc, index) : getIthJsonValueFromContainer((JsonContainer *) jbc, index); @@ -3069,10 +3088,10 @@ static JsonItem * getScalar(JsonItem *scalar, enum jbvType type) { /* Scalars should be always extracted during jsonpath execution. */ - Assert(scalar->type != jbvBinary || - !JsonContainerIsScalar(scalar->jbv.val.binary.data)); + Assert(!JsonItemIsBinary(scalar) || + !JsonContainerIsScalar(JsonItemBinary(scalar).data)); - return scalar->type == type ? scalar : NULL; + return JsonItemGetType(scalar) == type ? scalar : NULL; } /* Construct a JSON array from the item list */ @@ -3347,3 +3366,43 @@ tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, return !error; } + +static void +JsonItemInitNull(JsonItem *item) +{ + item->type = jbvNull; +} + +static void +JsonItemInitBool(JsonItem *item, bool val) +{ + item->type = jbvBool; + JsonItemBool(item) = val; +} + +static void +JsonItemInitNumeric(JsonItem *item, Numeric val) +{ + item->type = jbvNumeric; + JsonItemNumeric(item) = val; +} + +#define JsonItemInitNumericDatum(item, val) JsonItemInitNumeric(item, DatumGetNumeric(val)) + +static void +JsonItemInitString(JsonItem *item, char *str, int len) +{ + item->type = jbvString; + JsonItemString(item).val = str; + JsonItemString(item).len = len; +} + +static void +JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) +{ + item->type = jsiDatetime; + JsonItemDatetime(item).value = val; + JsonItemDatetime(item).typid = typid; + JsonItemDatetime(item).typmod = typmod; + JsonItemDatetime(item).tz = tz; +} From c2c0c73b69a863b0c65092ddd961a5217a4fd25d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 28 Feb 2019 21:32:01 +0300 Subject: [PATCH 19/66] Add next pointer to JsonItem --- src/backend/utils/adt/jsonpath_exec.c | 173 ++++++++++++-------------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 70cf7f366e..86cbf489d1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -102,23 +102,28 @@ typedef enum JsonItemType } JsonItemType; /* SQL/JSON item */ -typedef union JsonItem +typedef struct JsonItem { - int type; /* XXX JsonItemType */ + struct JsonItem *next; - JsonbValue jbv; - - struct + union { - int type; - Datum value; - Oid typid; - int32 typmod; - int tz; - } datetime; + int type; /* XXX JsonItemType */ + + JsonbValue jbv; + + struct + { + int type; + Datum value; + Oid typid; + int32 typmod; + int tz; + } datetime; + } val; } JsonItem; -#define JsonItemJbv(jsi) (&(jsi)->jbv) +#define JsonItemJbv(jsi) (&(jsi)->val.jbv) #define JsonItemBool(jsi) (JsonItemJbv(jsi)->val.boolean) #define JsonItemNumeric(jsi) (JsonItemJbv(jsi)->val.numeric) #define JsonItemNumericDatum(jsi) NumericGetDatum(JsonItemNumeric(jsi)) @@ -126,9 +131,9 @@ typedef union JsonItem #define JsonItemBinary(jsi) (JsonItemJbv(jsi)->val.binary) #define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array) #define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object) -#define JsonItemDatetime(jsi) ((jsi)->datetime) +#define JsonItemDatetime(jsi) ((jsi)->val.datetime) -#define JsonItemGetType(jsi) ((jsi)->type) +#define JsonItemGetType(jsi) ((jsi)->val.type) #define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull) #define JsonItemIsBool(jsi) (JsonItemGetType(jsi) == jsiBool) #define JsonItemIsNumeric(jsi) (JsonItemGetType(jsi) == jsiNumeric) @@ -140,8 +145,6 @@ typedef union JsonItem #define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \ JsonItemIsDatetime(jsi)) -#define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) - typedef union Jsonx { Jsonb jb; @@ -163,8 +166,6 @@ typedef struct JsonxIterator } it; } JsonxIterator; -#define JsonbValueToJsonItem(jbv) ((JsonItem *) (jbv)) - /* * Represents "base object" and it's "id" for .keyvalue() evaluation. */ @@ -246,14 +247,14 @@ typedef enum JsonPathExecResult */ typedef struct JsonValueList { - JsonItem *singleton; - List *list; + JsonItem *head; + JsonItem *tail; + int length; } JsonValueList; typedef struct JsonValueListIterator { - JsonItem *value; - ListCell *next; + JsonItem *next; } JsonValueListIterator; /* @@ -392,6 +393,7 @@ static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz); static JsonItem *copyJsonItem(JsonItem *src); +static JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); static Jsonb *JsonItemToJsonb(JsonItem *jsi); static const char *JsonItemTypeName(JsonItem *jsi); @@ -417,8 +419,9 @@ static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, - bool isJsonb); -static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb); + bool isJsonb, JsonItem *val); +static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, + JsonItem *elem); static void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb); @@ -950,18 +953,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiKey: if (JsonbType(jb) == jbvObject) { + JsonItem val; int keylen; char *key = jspGetString(jsp, &keylen); - jb = getJsonObjectKey(jb, key, keylen, cxt->isJsonb); + jb = getJsonObjectKey(jb, key, keylen, cxt->isJsonb, &val); if (jb != NULL) { - res = executeNextItem(cxt, jsp, NULL, jb, found, false); - - /* free value if it was not added to found list */ - if (jspHasNext(jsp) || !found) - pfree(jb); + res = executeNextItem(cxt, jsp, NULL, jb, found, true); } else if (!jspIgnoreStructuralErrors(cxt)) { @@ -1072,30 +1072,27 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, for (index = index_from; index <= index_to; index++) { + JsonItem jsibuf; JsonItem *jsi; - bool copy; if (singleton) { jsi = jb; - copy = true; } else { jsi = getJsonArrayElement(jb, (uint32) index, - cxt->isJsonb); + cxt->isJsonb, &jsibuf); if (jsi == NULL) continue; - - copy = false; } if (!hasNext && !found) return jperOk; res = executeNextItem(cxt, jsp, &elem, jsi, found, - copy); + true); if (jperIsError(res)) break; @@ -1154,8 +1151,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { bool hasNext = jspGetNext(jsp, &elem); - if (jb->type != jbvBinary) - elog(ERROR, "invalid jsonb object type: %d", jb->type); + if (!JsonItemIsBinary(jb)) + elog(ERROR, "invalid jsonb object type: %d", + JsonItemGetType(jb)); return executeAnyItem (cxt, hasNext ? &elem : NULL, @@ -1567,8 +1565,8 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (!JsonItemIsBinary(jb)) { - Assert(jb->type != jbvArray); - elog(ERROR, "invalid jsonb array value type: %d", jb->type); + Assert(!JsonItemIsArray(jb)); + elog(ERROR, "invalid jsonb array value type: %d", JsonItemGetType(jb)); } return executeAnyItem @@ -2286,7 +2284,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, int64 id; bool hasNext; - if (JsonbType(jb) != jbvObject || jb->type != jbvBinary) + if (JsonbType(jb) != jbvObject || !JsonItemIsBinary(jb)) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND), errmsg("jsonpath item method .%s() can only be applied to an object", @@ -2523,7 +2521,7 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, JsonInitBinary(baseObject, js); } - value->jbv = *val; + *JsonItemJbv(value) = *val; pfree(val); return 1; @@ -2688,6 +2686,13 @@ copyJsonItem(JsonItem *src) return dst; } +static JsonItem * +JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi) +{ + *JsonItemJbv(jsi) = *jbv; + return jsi; +} + static JsonbValue * JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) { @@ -2796,64 +2801,58 @@ setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id) } static void -JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv) +JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi) { - if (jvl->singleton) + jsi->next = NULL; + + if (jvl->tail) { - jvl->list = list_make2(jvl->singleton, jbv); - jvl->singleton = NULL; + jvl->tail->next = jsi; + jvl->tail = jsi; } - else if (!jvl->list) - jvl->singleton = jbv; else - jvl->list = lappend(jvl->list, jbv); + { + Assert(!jvl->head); + jvl->head = jvl->tail = jsi; + } + + jvl->length++; } static int JsonValueListLength(const JsonValueList *jvl) { - return jvl->singleton ? 1 : list_length(jvl->list); + return jvl->length; } static bool JsonValueListIsEmpty(JsonValueList *jvl) { - return !jvl->singleton && list_length(jvl->list) <= 0; + return !jvl->length; } static JsonItem * JsonValueListHead(JsonValueList *jvl) { - return jvl->singleton ? jvl->singleton : linitial(jvl->list); + return jvl->head; } static List * JsonValueListGetList(JsonValueList *jvl) { - if (jvl->singleton) - return list_make1(jvl->singleton); + List *list = NIL; + JsonItem *jsi; + + for (jsi = jvl->head; jsi; jsi = jsi->next) + list = lappend(list, jsi); - return jvl->list; + return list; } static void JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) { - if (jvl->singleton) - { - it->value = jvl->singleton; - it->next = NULL; - } - else if (list_head(jvl->list) != NULL) - { - it->value = (JsonItem *) linitial(jvl->list); - it->next = lnext(list_head(jvl->list)); - } - else - { - it->value = NULL; - it->next = NULL; - } + it->next = jvl->head; } /* @@ -2862,17 +2861,10 @@ JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) static JsonItem * JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) { - JsonItem *result = it->value; + JsonItem *result = it->next; - if (it->next) - { - it->value = lfirst(it->next); - it->next = lnext(it->next); - } - else - { - it->value = NULL; - } + if (result) + it->next = result->next; return result; } @@ -3005,9 +2997,10 @@ JsonItemUnquoteText(JsonItem *jsi, bool isJsonb) } static JsonItem * -getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb) +getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb, + JsonItem *res) { - JsonbContainer *jbc = jsi->jbv.val.binary.data; + JsonbContainer *jbc = JsonItemBinary(jsi).data; JsonbValue *val; JsonbValue key; @@ -3019,18 +3012,18 @@ getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb) findJsonbValueFromContainer(jbc, JB_FOBJECT, &key) : findJsonValueFromContainer((JsonContainer *) jbc, JB_FOBJECT, &key); - return val ? JsonbValueToJsonItem(val) : NULL; + return val ? JsonbValueToJsonItem(val, res) : NULL; } static JsonItem * -getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb) +getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, JsonItem *res) { JsonbContainer *jbc = JsonItemBinary(jb).data; JsonbValue *elem = isJsonb ? getIthJsonbValueFromContainer(jbc, index) : getIthJsonValueFromContainer((JsonContainer *) jbc, index); - return elem ? JsonbValueToJsonItem(elem) : NULL; + return elem ? JsonbValueToJsonItem(elem, res) : NULL; } static inline void @@ -3370,20 +3363,20 @@ tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, static void JsonItemInitNull(JsonItem *item) { - item->type = jbvNull; + item->val.type = jbvNull; } static void JsonItemInitBool(JsonItem *item, bool val) { - item->type = jbvBool; + item->val.type = jbvBool; JsonItemBool(item) = val; } static void JsonItemInitNumeric(JsonItem *item, Numeric val) { - item->type = jbvNumeric; + item->val.type = jbvNumeric; JsonItemNumeric(item) = val; } @@ -3392,7 +3385,7 @@ JsonItemInitNumeric(JsonItem *item, Numeric val) static void JsonItemInitString(JsonItem *item, char *str, int len) { - item->type = jbvString; + item->val.type = jbvString; JsonItemString(item).val = str; JsonItemString(item).len = len; } @@ -3400,7 +3393,7 @@ JsonItemInitString(JsonItem *item, char *str, int len) static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) { - item->type = jsiDatetime; + item->val.type = jsiDatetime; JsonItemDatetime(item).value = val; JsonItemDatetime(item).typid = typid; JsonItemDatetime(item).typmod = typmod; From 9734c0027e6f1a8504391321650fc789f7afaff9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 02:18:44 +0300 Subject: [PATCH 20/66] Add invisible internal coercion form --- contrib/postgres_fdw/deparse.c | 6 +- src/backend/utils/adt/ruleutils.c | 111 +++++++++++------------------- src/include/nodes/primnodes.h | 3 +- 3 files changed, 45 insertions(+), 75 deletions(-) diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 6da4c834bf..019faa687e 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -2634,7 +2634,8 @@ deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) * If the function call came from an implicit coercion, then just show the * first argument. */ - if (node->funcformat == COERCE_IMPLICIT_CAST) + if (node->funcformat == COERCE_IMPLICIT_CAST || + node->funcformat == COERCE_INTERNAL_CAST) { deparseExpr((Expr *) linitial(node->args), context); return; @@ -2831,7 +2832,8 @@ static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context) { deparseExpr(node->arg, context); - if (node->relabelformat != COERCE_IMPLICIT_CAST) + if (node->relabelformat != COERCE_IMPLICIT_CAST && + node->relabelformat == COERCE_INTERNAL_CAST) appendStringInfo(context->buf, "::%s", deparse_type_name(node->resulttype, node->resulttypmod)); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b9fdd99db8..2ecd959fec 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7639,8 +7639,10 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) CoercionForm type = ((FuncExpr *) parentNode)->funcformat; if (type == COERCE_EXPLICIT_CAST || - type == COERCE_IMPLICIT_CAST) + type == COERCE_IMPLICIT_CAST || + type == COERCE_INTERNAL_CAST) return false; + return true; /* own parentheses */ } case T_BoolExpr: /* lower precedence */ @@ -7690,7 +7692,8 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) CoercionForm type = ((FuncExpr *) parentNode)->funcformat; if (type == COERCE_EXPLICIT_CAST || - type == COERCE_IMPLICIT_CAST) + type == COERCE_IMPLICIT_CAST || + type == COERCE_INTERNAL_CAST) return false; return true; /* own parentheses */ } @@ -7815,6 +7818,25 @@ get_rule_expr_paren(Node *node, deparse_context *context, } +/* + * get_coercion - Parse back a coercion + */ +static void +get_coercion(Expr *arg, deparse_context *context, bool showimplicit, + Node *node, CoercionForm format, Oid typid, int32 typmod) +{ + if (format == COERCE_INTERNAL_CAST || + (format == COERCE_IMPLICIT_CAST && !showimplicit)) + { + /* don't show the implicit cast */ + get_rule_expr_paren((Node *) arg, context, false, node); + } + else + { + get_coercion_expr((Node *) arg, context, typid, typmod, node); + } +} + /* ---------- * get_rule_expr - Parse back an expression * @@ -8197,83 +8219,38 @@ get_rule_expr(Node *node, deparse_context *context, case T_RelabelType: { RelabelType *relabel = (RelabelType *) node; - Node *arg = (Node *) relabel->arg; - if (relabel->relabelformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - relabel->resulttype, - relabel->resulttypmod, - node); - } + get_coercion(relabel->arg, context, showimplicit, node, + relabel->relabelformat, relabel->resulttype, + relabel->resulttypmod); } break; case T_CoerceViaIO: { CoerceViaIO *iocoerce = (CoerceViaIO *) node; - Node *arg = (Node *) iocoerce->arg; - if (iocoerce->coerceformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - iocoerce->resulttype, - -1, - node); - } + get_coercion(iocoerce->arg, context, showimplicit, node, + iocoerce->coerceformat, iocoerce->resulttype, -1); } break; case T_ArrayCoerceExpr: { ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; - Node *arg = (Node *) acoerce->arg; - if (acoerce->coerceformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - acoerce->resulttype, - acoerce->resulttypmod, - node); - } + get_coercion(acoerce->arg, context, showimplicit, node, + acoerce->coerceformat, acoerce->resulttype, + acoerce->resulttypmod); } break; case T_ConvertRowtypeExpr: { ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; - Node *arg = (Node *) convert->arg; - if (convert->convertformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr_paren(arg, context, false, node); - } - else - { - get_coercion_expr(arg, context, - convert->resulttype, -1, - node); - } + get_coercion(convert->arg, context, showimplicit, node, + convert->convertformat, convert->resulttype, -1); } break; @@ -8826,21 +8803,10 @@ get_rule_expr(Node *node, deparse_context *context, case T_CoerceToDomain: { CoerceToDomain *ctest = (CoerceToDomain *) node; - Node *arg = (Node *) ctest->arg; - if (ctest->coercionformat == COERCE_IMPLICIT_CAST && - !showimplicit) - { - /* don't show the implicit cast */ - get_rule_expr(arg, context, false); - } - else - { - get_coercion_expr(arg, context, - ctest->resulttype, - ctest->resulttypmod, - node); - } + get_coercion(ctest->arg, context, showimplicit, node, + ctest->coercionformat, ctest->resulttype, + ctest->resulttypmod); } break; @@ -9172,7 +9138,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context, * If the function call came from an implicit coercion, then just show the * first argument --- unless caller wants to see implicit coercions. */ - if (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit) + if (expr->funcformat == COERCE_INTERNAL_CAST || + (expr->funcformat == COERCE_IMPLICIT_CAST && !showimplicit)) { get_rule_expr_paren((Node *) linitial(expr->args), context, false, (Node *) expr); diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7c278c0e56..97240e2122 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -443,7 +443,8 @@ typedef enum CoercionForm { COERCE_EXPLICIT_CALL, /* display as a function call */ COERCE_EXPLICIT_CAST, /* display as an explicit cast */ - COERCE_IMPLICIT_CAST /* implicit cast, so hide it */ + COERCE_IMPLICIT_CAST, /* implicit cast, so hide it */ + COERCE_INTERNAL_CAST /* internal cast, so hide it always */ } CoercionForm; /* From 1bc2e213669a75053fc7f64d54081d19b204a658 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 02:32:55 +0300 Subject: [PATCH 21/66] Add function formats --- .../pg_stat_statements/pg_stat_statements.c | 6 +++ src/backend/nodes/copyfuncs.c | 6 +++ src/backend/nodes/equalfuncs.c | 6 +++ src/backend/nodes/outfuncs.c | 6 +++ src/backend/nodes/readfuncs.c | 6 +++ src/backend/optimizer/util/clauses.c | 4 ++ src/backend/utils/adt/ruleutils.c | 37 ++++++++++++++++--- src/include/nodes/primnodes.h | 11 ++++++ 8 files changed, 76 insertions(+), 6 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 26610b34b6..312d70e469 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2559,11 +2559,13 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) Aggref *expr = (Aggref *) node; APP_JUMB(expr->aggfnoid); + APP_JUMB(expr->aggformat); JumbleExpr(jstate, (Node *) expr->aggdirectargs); JumbleExpr(jstate, (Node *) expr->args); JumbleExpr(jstate, (Node *) expr->aggorder); JumbleExpr(jstate, (Node *) expr->aggdistinct); JumbleExpr(jstate, (Node *) expr->aggfilter); + JumbleExpr(jstate, (Node *) expr->aggformatopts); } break; case T_GroupingFunc: @@ -2579,8 +2581,10 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) APP_JUMB(expr->winfnoid); APP_JUMB(expr->winref); + APP_JUMB(expr->winformat); JumbleExpr(jstate, (Node *) expr->args); JumbleExpr(jstate, (Node *) expr->aggfilter); + JumbleExpr(jstate, (Node *) expr->winformatopts); } break; case T_SubscriptingRef: @@ -2598,7 +2602,9 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) FuncExpr *expr = (FuncExpr *) node; APP_JUMB(expr->funcid); + APP_JUMB(expr->funcformat2); JumbleExpr(jstate, (Node *) expr->args); + JumbleExpr(jstate, (Node *) expr->funcformatopts); } break; case T_NamedArgExpr: diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 78deade89b..73bc02a5c5 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1447,6 +1447,8 @@ _copyAggref(const Aggref *from) COPY_SCALAR_FIELD(aggkind); COPY_SCALAR_FIELD(agglevelsup); COPY_SCALAR_FIELD(aggsplit); + COPY_SCALAR_FIELD(aggformat); + COPY_NODE_FIELD(aggformatopts); COPY_LOCATION_FIELD(location); return newnode; @@ -1486,6 +1488,8 @@ _copyWindowFunc(const WindowFunc *from) COPY_SCALAR_FIELD(winref); COPY_SCALAR_FIELD(winstar); COPY_SCALAR_FIELD(winagg); + COPY_SCALAR_FIELD(winformat); + COPY_NODE_FIELD(winformatopts); COPY_LOCATION_FIELD(location); return newnode; @@ -1527,6 +1531,8 @@ _copyFuncExpr(const FuncExpr *from) COPY_SCALAR_FIELD(funccollid); COPY_SCALAR_FIELD(inputcollid); COPY_NODE_FIELD(args); + COPY_SCALAR_FIELD(funcformat2); + COPY_NODE_FIELD(funcformatopts); COPY_LOCATION_FIELD(location); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4f2ebe5118..0d1cb840d7 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -228,6 +228,8 @@ _equalAggref(const Aggref *a, const Aggref *b) COMPARE_SCALAR_FIELD(aggkind); COMPARE_SCALAR_FIELD(agglevelsup); COMPARE_SCALAR_FIELD(aggsplit); + COMPARE_SCALAR_FIELD(aggformat); + COMPARE_NODE_FIELD(aggformatopts); COMPARE_LOCATION_FIELD(location); return true; @@ -260,6 +262,8 @@ _equalWindowFunc(const WindowFunc *a, const WindowFunc *b) COMPARE_SCALAR_FIELD(winref); COMPARE_SCALAR_FIELD(winstar); COMPARE_SCALAR_FIELD(winagg); + COMPARE_SCALAR_FIELD(winformat); + COMPARE_NODE_FIELD(winformatopts); COMPARE_LOCATION_FIELD(location); return true; @@ -291,6 +295,8 @@ _equalFuncExpr(const FuncExpr *a, const FuncExpr *b) COMPARE_SCALAR_FIELD(funccollid); COMPARE_SCALAR_FIELD(inputcollid); COMPARE_NODE_FIELD(args); + COMPARE_SCALAR_FIELD(funcformat2); + COMPARE_NODE_FIELD(funcformatopts); COMPARE_LOCATION_FIELD(location); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 8400dd319e..2be2cfac8b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1126,6 +1126,8 @@ _outAggref(StringInfo str, const Aggref *node) WRITE_CHAR_FIELD(aggkind); WRITE_UINT_FIELD(agglevelsup); WRITE_ENUM_FIELD(aggsplit, AggSplit); + WRITE_ENUM_FIELD(aggformat, FuncFormat); + WRITE_NODE_FIELD(aggformatopts); WRITE_LOCATION_FIELD(location); } @@ -1155,6 +1157,8 @@ _outWindowFunc(StringInfo str, const WindowFunc *node) WRITE_UINT_FIELD(winref); WRITE_BOOL_FIELD(winstar); WRITE_BOOL_FIELD(winagg); + WRITE_ENUM_FIELD(winformat, FuncFormat); + WRITE_NODE_FIELD(winformatopts); WRITE_LOCATION_FIELD(location); } @@ -1186,6 +1190,8 @@ _outFuncExpr(StringInfo str, const FuncExpr *node) WRITE_OID_FIELD(funccollid); WRITE_OID_FIELD(inputcollid); WRITE_NODE_FIELD(args); + WRITE_ENUM_FIELD(funcformat2, FuncFormat); + WRITE_NODE_FIELD(funcformatopts); WRITE_LOCATION_FIELD(location); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6c2626ee62..6ca26af9ec 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -614,6 +614,8 @@ _readAggref(void) READ_CHAR_FIELD(aggkind); READ_UINT_FIELD(agglevelsup); READ_ENUM_FIELD(aggsplit, AggSplit); + READ_ENUM_FIELD(aggformat, FuncFormat); + READ_NODE_FIELD(aggformatopts); READ_LOCATION_FIELD(location); READ_DONE(); @@ -653,6 +655,8 @@ _readWindowFunc(void) READ_UINT_FIELD(winref); READ_BOOL_FIELD(winstar); READ_BOOL_FIELD(winagg); + READ_ENUM_FIELD(winformat, FuncFormat); + READ_NODE_FIELD(winformatopts); READ_LOCATION_FIELD(location); READ_DONE(); @@ -694,6 +698,8 @@ _readFuncExpr(void) READ_OID_FIELD(funccollid); READ_OID_FIELD(inputcollid); READ_NODE_FIELD(args); + READ_ENUM_FIELD(funcformat2, FuncFormat); + READ_NODE_FIELD(funcformatopts); READ_LOCATION_FIELD(location); READ_DONE(); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 2e84d6b3b4..789908249d 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2453,6 +2453,8 @@ eval_const_expressions_mutator(Node *node, newexpr->winref = expr->winref; newexpr->winstar = expr->winstar; newexpr->winagg = expr->winagg; + newexpr->winformat = expr->winformat; + newexpr->winformatopts = copyObject(expr->winformatopts); newexpr->location = expr->location; return (Node *) newexpr; @@ -2499,6 +2501,8 @@ eval_const_expressions_mutator(Node *node, newexpr->funccollid = expr->funccollid; newexpr->inputcollid = expr->inputcollid; newexpr->args = args; + newexpr->funcformat2 = expr->funcformat2; + newexpr->funcformatopts = copyObject(expr->funcformatopts); newexpr->location = expr->location; return (Node *) newexpr; } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2ecd959fec..d741c02d8b 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9119,6 +9119,16 @@ get_oper_expr(OpExpr *expr, deparse_context *context) appendStringInfoChar(buf, ')'); } +static void +get_func_opts(FuncFormat aggformat, Node *aggformatopts, deparse_context *context) +{ + switch (aggformat) + { + default: + break; + } +} + /* * get_func_expr - Parse back a FuncExpr node */ @@ -9133,6 +9143,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context, List *argnames; bool use_variadic; ListCell *l; + const char *funcname; /* * If the function call came from an implicit coercion, then just show the @@ -9187,12 +9198,19 @@ get_func_expr(FuncExpr *expr, deparse_context *context, nargs++; } - appendStringInfo(buf, "%s(", - generate_function_name(funcoid, nargs, - argnames, argtypes, - expr->funcvariadic, - &use_variadic, - context->special_exprkind)); + switch (expr->funcformat2) + { + default: + funcname = generate_function_name(funcoid, nargs, + argnames, argtypes, + expr->funcvariadic, + &use_variadic, + context->special_exprkind); + break; + } + + appendStringInfo(buf, "%s(", funcname); + nargs = 0; foreach(l, expr->args) { @@ -9202,6 +9220,9 @@ get_func_expr(FuncExpr *expr, deparse_context *context, appendStringInfoString(buf, "VARIADIC "); get_rule_expr((Node *) lfirst(l), context, true); } + + get_func_opts(expr->funcformat2, expr->funcformatopts, context); + appendStringInfoChar(buf, ')'); } @@ -9300,6 +9321,8 @@ get_agg_expr(Aggref *aggref, deparse_context *context, } } + get_func_opts(aggref->aggformat, aggref->aggformatopts, context); + if (aggref->aggfilter != NULL) { appendStringInfoString(buf, ") FILTER (WHERE "); @@ -9366,6 +9389,8 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) else get_rule_expr((Node *) wfunc->args, context, true); + get_func_opts(wfunc->winformat, wfunc->winformatopts, context); + if (wfunc->aggfilter != NULL) { appendStringInfoString(buf, ") FILTER (WHERE "); diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 97240e2122..2691872f1a 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -253,6 +253,11 @@ typedef struct Param int location; /* token location, or -1 if unknown */ } Param; +typedef enum FuncFormat +{ + FUNCFMT_REGULAR = 0, +} FuncFormat; + /* * Aggref * @@ -312,6 +317,8 @@ typedef struct Aggref char aggkind; /* aggregate kind (see pg_aggregate.h) */ Index agglevelsup; /* > 0 if agg belongs to outer query */ AggSplit aggsplit; /* expected agg-splitting mode of parent Agg */ + FuncFormat aggformat; /* how to display this aggregate */ + Node *aggformatopts; /* display options, if any */ int location; /* token location, or -1 if unknown */ } Aggref; @@ -365,6 +372,8 @@ typedef struct WindowFunc Index winref; /* index of associated WindowClause */ bool winstar; /* true if argument list was really '*' */ bool winagg; /* is function a simple aggregate? */ + FuncFormat winformat; /* how to display this window function */ + Node *winformatopts; /* display options, if any */ int location; /* token location, or -1 if unknown */ } WindowFunc; @@ -462,6 +471,8 @@ typedef struct FuncExpr Oid funccollid; /* OID of collation of result */ Oid inputcollid; /* OID of collation that function should use */ List *args; /* arguments to the function */ + FuncFormat funcformat2; /* how to display this function call */ + Node *funcformatopts; /* display options, if any */ int location; /* token location, or -1 if unknown */ } FuncExpr; From 992e20297ebd225b7b1d0da3243543332c40475f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Feb 2017 18:41:57 +0300 Subject: [PATCH 22/66] Add SQL/JSON parsing --- src/backend/nodes/makefuncs.c | 85 ++++ src/backend/parser/gram.y | 696 ++++++++++++++++++++++++++- src/backend/parser/parser.c | 18 +- src/include/nodes/makefuncs.h | 7 + src/include/nodes/nodes.h | 14 + src/include/nodes/parsenodes.h | 201 ++++++++ src/include/nodes/primnodes.h | 105 ++++ src/include/parser/kwlist.h | 20 + src/interfaces/ecpg/preproc/parse.pl | 2 + src/interfaces/ecpg/preproc/parser.c | 17 + 10 files changed, 1145 insertions(+), 20 deletions(-) diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 7085ed2c4c..64f88b9cd0 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -20,6 +20,7 @@ #include "fmgr.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" +#include "utils/errcodes.h" #include "utils/lsyscache.h" @@ -762,3 +763,87 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols) v->va_cols = va_cols; return v; } + +/* + * makeJsonValueExpr - + * creates a JsonValueExpr node + */ +JsonValueExpr * +makeJsonValueExpr(Expr *expr, JsonFormat format) +{ + JsonValueExpr *jve = makeNode(JsonValueExpr); + + jve->expr = expr; + 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 vtype, + bool unique_keys) +{ + JsonIsPredicate *n = makeNode(JsonIsPredicate); + + n->expr = expr; + n->format = format; + n->vtype = vtype; + n->unique_keys = unique_keys; + + return (Node *) n; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8311b1dd46..64b678c406 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -212,6 +212,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JoinType jtype; DropBehavior dbehavior; OnCommitAction oncommit; + JsonFormat jsformat; List *list; Node *node; Value *value; @@ -242,6 +243,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); PartitionSpec *partspec; PartitionBoundSpec *partboundspec; RoleSpec *rolespec; + JsonBehavior *jsbehavior; + struct { + JsonBehavior *on_empty; + JsonBehavior *on_error; + } on_behavior; + JsonQuotes js_quotes; } %type stmt schema_stmt @@ -590,6 +597,72 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type hash_partbound %type hash_partbound_elem +%type json_value_expr + json_func_expr + json_value_func_expr + json_query_expr + json_exists_predicate + json_api_common_syntax + json_context_item + json_argument + json_output_clause_opt + json_value_constructor + json_object_constructor + json_object_constructor_args_opt + json_object_args + json_object_ctor_args_opt + json_object_func_args + json_array_constructor + json_name_and_value + json_aggregate_func + json_object_aggregate_constructor + json_array_aggregate_constructor + +%type json_arguments + json_passing_clause_opt + json_name_and_value_list + json_value_expr_list + json_array_aggregate_order_by_clause_opt + +%type json_returning_clause_opt + +%type json_path_specification + 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_format_clause_opt + json_representation + +%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 @@ -612,7 +685,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 ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION @@ -622,8 +695,8 @@ 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 CONCURRENTLY CONFIGURATION CONFLICT CONNECTION CONSTRAINT - CONSTRAINTS CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE + COMMITTED 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 CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE @@ -633,12 +706,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 + EACH ELSE EMPTY_P ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ERROR_P ESCAPE + EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FILTER 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 @@ -649,9 +722,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_VALUE - KEY + KEY KEYS KEEP LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -663,7 +737,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 @@ -671,17 +745,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 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 @@ -689,8 +763,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P TYPES_P - UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED - UNTIL UPDATE USER USING + UNBOUNDED UNCOMMITTED UNCONDITIONAL UNENCRYPTED UNION UNIQUE UNKNOWN + UNLISTEN UNLOGGED UNTIL UPDATE USER USING VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE @@ -714,11 +788,11 @@ 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 /* Precedence: lowest to highest */ %nonassoc SET /* see relation_expr_opt_alias */ +%right FORMAT %left UNION EXCEPT %left INTERSECT %left OR @@ -757,6 +831,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * blame any funny behavior of UNBOUNDED on the SQL standard, though. */ %nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */ +%nonassoc ERROR_P EMPTY_P DEFAULT ABSENT /* JSON error/empty behavior */ %nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' @@ -781,6 +856,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */ %right PRESERVE STRIP_P +%nonassoc empty_json_unique +%left WITHOUT WITH_LA_UNIQUE + %% /* @@ -12843,7 +12921,7 @@ ConstInterval: opt_timezone: WITH_LA TIME ZONE { $$ = true; } - | WITHOUT TIME ZONE { $$ = false; } + | WITHOUT_LA TIME ZONE { $$ = false; } | /*EMPTY*/ { $$ = false; } ; @@ -13344,6 +13422,48 @@ a_expr: c_expr { $$ = $1; } list_make1($1), @2), @2); } + | a_expr + IS JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; + $$ = makeJsonIsPredicate($1, format, $4, $5); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeJsonIsPredicate($1, $3, $6, $7); + } + */ + | a_expr + IS NOT JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; + $$ = makeNotExpr(makeJsonIsPredicate($1, format, $5, $6), @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS NOT JSON + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $7, $8), @1); + } + */ | DEFAULT { /* @@ -13436,6 +13556,25 @@ b_expr: c_expr } ; +json_predicate_type_constraint_opt: + VALUE_P { $$ = JS_TYPE_ANY; } + | ARRAY { $$ = JS_TYPE_ARRAY; } + | OBJECT_P { $$ = JS_TYPE_OBJECT; } + | SCALAR { $$ = JS_TYPE_SCALAR; } + | /* EMPTY */ { $$ = JS_TYPE_ANY; } + ; + +json_key_uniqueness_constraint_opt: + WITH_LA_UNIQUE UNIQUE opt_keys { $$ = true; } + | WITHOUT UNIQUE opt_keys { $$ = false; } + | /* EMPTY */ %prec empty_json_unique { $$ = false; } + ; + +opt_keys: + KEYS { } + | /* EMPTY */ { } + ; + /* * Productions that can be used in both a_expr and b_expr. * @@ -13696,6 +13835,13 @@ func_expr: func_application within_group_clause filter_clause over_clause n->over = $4; $$ = (Node *) n; } + | json_aggregate_func filter_clause over_clause + { + JsonAggCtor *n = (JsonAggCtor *) $1; + n->agg_filter = $2; + n->over = $3; + $$ = (Node *) $1; + } | func_expr_common_subexpr { $$ = $1; } ; @@ -13709,6 +13855,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; } ; /* @@ -13930,6 +14077,8 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | json_func_expr + { $$ = $1; } ; /* @@ -14623,6 +14772,495 @@ opt_asymmetric: ASYMMETRIC | /*EMPTY*/ ; +/* SQL/JSON support */ +json_func_expr: + json_value_func_expr + | json_query_expr + | json_exists_predicate + | json_value_constructor + ; + + +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; + if ($4) + { + n->output = (JsonOutput *) makeNode(JsonOutput); + n->output->typeName = $4; + n->output->returning.format.location = @4; + n->output->returning.format.type = JS_FORMAT_DEFAULT; + n->output->returning.format.encoding = JS_ENC_DEFAULT; + } + else + n->output = NULL; + 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: + Sconst { $$ = $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, $2); + } + ; + +json_format_clause_opt: + FORMAT json_representation + { + $$ = $2; + $$.location = @1; + } + | /* EMPTY */ + { + $$.type = JS_FORMAT_DEFAULT; + $$.encoding = JS_ENC_DEFAULT; + $$.location = -1; + } + ; + +json_representation: + JSON json_encoding_clause_opt + { + $$.type = JS_FORMAT_JSON; + $$.encoding = $2; + $$.location = @1; + } + /* | implementation_defined_JSON_representation_option (BSON, AVRO etc) */ + ; + +json_encoding_clause_opt: + ENCODING json_encoding { $$ = $2; } + | /* EMPTY */ { $$ = JS_ENC_DEFAULT; } + ; + +json_encoding: + name { $$ = makeJsonEncoding($1); } + /* + | UTF8 { $$ = JS_ENC_UTF8; } + | UTF16 { $$ = JS_ENC_UTF16; } + | UTF32 { $$ = JS_ENC_UTF32; } + */ + ; + +json_returning_clause_opt: + RETURNING Typename { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + +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); } + ; + +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_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_output_clause_opt: + RETURNING Typename json_format_clause_opt + { + JsonOutput *n = makeNode(JsonOutput); + n->typeName = $2; + n->returning.format = $3; + $$ = (Node *) n; + } + | /* EMPTY */ { $$ = NULL; } + ; + +json_exists_predicate: + JSON_EXISTS '(' + json_api_common_syntax + json_exists_error_clause_opt + ')' + { + JsonFuncExpr *p = makeNode(JsonFuncExpr); + p->op = IS_JSON_EXISTS; + p->common = (JsonCommon *) $3; + p->on_error = $4; + 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_ctor_args_opt + | json_object_func_args + ; + +json_object_func_args: + func_arg_list + { + List *func = list_make1(makeString("json_object")); + $$ = (Node *) makeFuncCall(func, $1, @1); + } + ; + +json_object_ctor_args_opt: + json_object_constructor_args_opt json_output_clause_opt + { + JsonObjectCtor *n = (JsonObjectCtor *) $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 + { + JsonObjectCtor *n = makeNode(JsonObjectCtor); + n->exprs = $1; + n->absent_on_null = $2; + n->unique = $3; + $$ = (Node *) n; + } + | /* EMPTY */ + { + JsonObjectCtor *n = makeNode(JsonObjectCtor); + 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 + 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 + ')' + { + JsonArrayCtor *n = makeNode(JsonArrayCtor); + 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 + ')' + { + JsonArrayQueryCtor *n = makeNode(JsonArrayQueryCtor); + n->query = $3; + /* n->format = $4; */ + n->absent_on_null = true /* $5 */; + n->output = (JsonOutput *) $4; + n->location = @1; + $$ = (Node *) n; + } + | JSON_ARRAY '(' + json_output_clause_opt + ')' + { + JsonArrayCtor *n = makeNode(JsonArrayCtor); + 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->ctor.output = (JsonOutput *) $6; + n->ctor.agg_order = NULL; + n->ctor.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->ctor.agg_order = $4; + n->absent_on_null = $5; + n->ctor.output = (JsonOutput *) $6; + n->ctor.location = @1; + $$ = (Node *) n; + } + ; + +json_array_aggregate_order_by_clause_opt: + ORDER BY sortby_list { $$ = $3; } + | /* EMPTY */ { $$ = NIL; } + ; /***************************************************************************** * @@ -15019,6 +15657,7 @@ ColLabel: IDENT { $$ = $1; } */ unreserved_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -15055,6 +15694,7 @@ unreserved_keyword: | COMMENTS | COMMIT | COMMITTED + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -15090,10 +15730,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -15139,7 +15781,10 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | JSON + | KEEP | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -15177,6 +15822,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -15206,6 +15852,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -15234,6 +15881,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -15284,6 +15932,7 @@ unreserved_keyword: | TYPES_P | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -15341,6 +15990,13 @@ col_name_keyword: | INT_P | INTEGER | INTERVAL + | JSON_ARRAY + | JSON_ARRAYAGG + | JSON_EXISTS + | JSON_OBJECT + | JSON_OBJECTAGG + | JSON_QUERY + | JSON_VALUE | LEAST | NATIONAL | NCHAR @@ -15392,6 +16048,7 @@ type_func_name_keyword: | CONCURRENTLY | CROSS | CURRENT_SCHEMA + | FORMAT | FREEZE | FULL | ILIKE @@ -15407,6 +16064,7 @@ type_func_name_keyword: | OVERLAPS | RIGHT | SIMILAR + | STRING | TABLESAMPLE | VERBOSE ; diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 4c0c258cd7..1b7f924838 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -24,7 +24,6 @@ #include "parser/gramparse.h" #include "parser/parser.h" - /* * raw_parser * Given a query in string form, do lexical and grammatical analysis. @@ -117,6 +116,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner) case WITH: cur_token_length = 4; break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -188,8 +190,22 @@ 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; + } return cur_token; diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index ad7b41d4aa..a15d91d5e1 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -100,4 +100,11 @@ extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int loc extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols); +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); +extern JsonEncoding makeJsonEncoding(char *name); + #endif /* MAKEFUNC_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 4e2fb39105..9461f5dfd9 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -476,6 +476,20 @@ typedef enum NodeTag T_PartitionRangeDatum, T_PartitionCmd, T_VacuumRelation, + T_JsonValueExpr, + T_JsonObjectCtor, + T_JsonArrayCtor, + T_JsonArrayQueryCtor, + 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 94ded3c135..6cbadf150f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1463,6 +1463,207 @@ 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; + +/* + * JsonValueExpr - + * representation of JSON value expression (expr [FORMAT json_format]) + */ +typedef struct JsonValueExpr +{ + NodeTag type; + Expr *expr; /* raw expression */ + JsonFormat format; /* FORMAT clause, if specified */ +} JsonValueExpr; + +/* + * 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 */ + JsonPathSpec pathspec; /* JSON path specification */ + 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; + +/* + * 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 vtype; /* JSON item type */ + bool unique_keys; /* check key uniqueness? */ + int location; /* token location, or -1 if unknown */ +} JsonIsPredicate; + +/* + * 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; + +/* + * JsonObjectCtor - + * untransformed representation of JSON_OBJECT() constructor + */ +typedef struct JsonObjectCtor +{ + 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 */ +} JsonObjectCtor; + +/* + * JsonArrayCtor - + * untransformed representation of JSON_ARRAY(element,...) constructor + */ +typedef struct JsonArrayCtor +{ + 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 */ +} JsonArrayCtor; + +/* + * JsonArrayQueryCtor - + * untransformed representation of JSON_ARRAY(subquery) constructor + */ +typedef struct JsonArrayQueryCtor +{ + 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 */ +} JsonArrayQueryCtor; + +/* + * JsonAggCtor - + * common fields of untransformed representation of + * JSON_ARRAYAGG() and JSON_OBJECTAGG() + */ +typedef struct JsonAggCtor +{ + 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 */ +} JsonAggCtor; + +/* + * JsonObjectAgg - + * untransformed representation of JSON_OBJECTAGG() + */ +typedef struct JsonObjectAgg +{ + JsonAggCtor ctor; /* 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 +{ + JsonAggCtor ctor; /* 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 2691872f1a..f27c5b30d9 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1187,6 +1187,111 @@ 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 + */ +typedef enum +{ + JSON_BEHAVIOR_NULL, + 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 +{ + JsonFormatType type; /* 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 +{ + JsonFormat format; /* output JSON format */ + Oid typid; /* target type Oid */ + int32 typmod; /* target type modifier */ +} JsonReturning; + +/* + * JsonBehavior - + * representation of JSON ON ... BEHAVIOR clause + */ +typedef struct JsonBehavior +{ + NodeTag type; + JsonBehaviorType btype; /* behavior type */ + Node *default_expr; /* default expression, if any */ +} JsonBehavior; + +/* + * JsonPassing - + * representation of JSON PASSING clause + */ +typedef struct JsonPassing +{ + List *values; /* list of PASSING argument expressions */ + List *names; /* parallel list of Value strings */ +} JsonPassing; + /* ---------------- * NullTest * diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 00ace8425e..51c1c7807a 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -26,6 +26,7 @@ /* name, value, category */ PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("absent", ABSENT, UNRESERVED_KEYWORD) PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD) PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD) PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD) @@ -88,6 +89,7 @@ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD) PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD) PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD) PG_KEYWORD("concurrently", CONCURRENTLY, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("conditional", CONDITIONAL, UNRESERVED_KEYWORD) PG_KEYWORD("configuration", CONFIGURATION, UNRESERVED_KEYWORD) PG_KEYWORD("conflict", CONFLICT, UNRESERVED_KEYWORD) PG_KEYWORD("connection", CONNECTION, UNRESERVED_KEYWORD) @@ -141,11 +143,13 @@ PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD) PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD) PG_KEYWORD("else", ELSE, RESERVED_KEYWORD) +PG_KEYWORD("empty", EMPTY_P, UNRESERVED_KEYWORD) PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("encoding", ENCODING, UNRESERVED_KEYWORD) PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD) PG_KEYWORD("end", END_P, RESERVED_KEYWORD) PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD) +PG_KEYWORD("error", ERROR_P, UNRESERVED_KEYWORD) PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD) PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD) PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD) @@ -168,6 +172,7 @@ PG_KEYWORD("following", FOLLOWING, UNRESERVED_KEYWORD) PG_KEYWORD("for", FOR, RESERVED_KEYWORD) PG_KEYWORD("force", FORCE, UNRESERVED_KEYWORD) PG_KEYWORD("foreign", FOREIGN, RESERVED_KEYWORD) +PG_KEYWORD("format", FORMAT, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("forward", FORWARD, UNRESERVED_KEYWORD) PG_KEYWORD("freeze", FREEZE, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("from", FROM, RESERVED_KEYWORD) @@ -220,7 +225,17 @@ PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("isolation", ISOLATION, UNRESERVED_KEYWORD) PG_KEYWORD("join", JOIN, TYPE_FUNC_NAME_KEYWORD) +PG_KEYWORD("json", JSON, UNRESERVED_KEYWORD) +PG_KEYWORD("json_array", JSON_ARRAY, COL_NAME_KEYWORD) +PG_KEYWORD("json_arrayagg", JSON_ARRAYAGG, COL_NAME_KEYWORD) +PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD) +PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD) +PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD) +PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD) +PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD) +PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) +PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD) PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD) PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD) PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD) @@ -276,6 +291,7 @@ PG_KEYWORD("off", OFF, UNRESERVED_KEYWORD) PG_KEYWORD("offset", OFFSET, RESERVED_KEYWORD) PG_KEYWORD("oids", OIDS, UNRESERVED_KEYWORD) PG_KEYWORD("old", OLD, UNRESERVED_KEYWORD) +PG_KEYWORD("omit", OMIT, UNRESERVED_KEYWORD) PG_KEYWORD("on", ON, RESERVED_KEYWORD) PG_KEYWORD("only", ONLY, RESERVED_KEYWORD) PG_KEYWORD("operator", OPERATOR, UNRESERVED_KEYWORD) @@ -317,6 +333,7 @@ PG_KEYWORD("procedures", PROCEDURES, UNRESERVED_KEYWORD) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD) PG_KEYWORD("publication", PUBLICATION, UNRESERVED_KEYWORD) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD) +PG_KEYWORD("quotes", QUOTES, UNRESERVED_KEYWORD) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD) PG_KEYWORD("read", READ, UNRESERVED_KEYWORD) PG_KEYWORD("real", REAL, COL_NAME_KEYWORD) @@ -350,6 +367,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD) PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD) PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD) PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD) +PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD) PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD) PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD) @@ -385,6 +403,7 @@ PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("string", STRING, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) @@ -418,6 +437,7 @@ PG_KEYWORD("type", TYPE_P, UNRESERVED_KEYWORD) PG_KEYWORD("types", TYPES_P, UNRESERVED_KEYWORD) PG_KEYWORD("unbounded", UNBOUNDED, UNRESERVED_KEYWORD) PG_KEYWORD("uncommitted", UNCOMMITTED, UNRESERVED_KEYWORD) +PG_KEYWORD("unconditional", UNCONDITIONAL, UNRESERVED_KEYWORD) PG_KEYWORD("unencrypted", UNENCRYPTED, UNRESERVED_KEYWORD) PG_KEYWORD("union", UNION, RESERVED_KEYWORD) PG_KEYWORD("unique", UNIQUE, RESERVED_KEYWORD) diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index 3619706cdc..541ae0bfe5 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -46,6 +46,8 @@ '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 abae89d51b..564e4f23c8 100644 --- a/src/interfaces/ecpg/preproc/parser.c +++ b/src/interfaces/ecpg/preproc/parser.c @@ -84,6 +84,9 @@ filtered_base_yylex(void) case WITH: cur_token_length = 4; break; + case WITHOUT: + cur_token_length = 7; + break; default: return cur_token; } @@ -155,8 +158,22 @@ 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; + } return cur_token; From fb2dc69d0ede5196fbaf4a6a5127cde11249f9e7 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 11 Feb 2017 03:07:36 +0300 Subject: [PATCH 23/66] Add JSON_OBJECT() transformation --- .../pg_stat_statements/pg_stat_statements.c | 21 + src/backend/executor/execExpr.c | 5 + src/backend/nodes/copyfuncs.c | 93 +++++ src/backend/nodes/equalfuncs.c | 31 ++ src/backend/nodes/nodeFuncs.c | 51 +++ src/backend/nodes/outfuncs.c | 31 ++ src/backend/nodes/readfuncs.c | 39 ++ src/backend/parser/parse_expr.c | 379 ++++++++++++++++++ src/backend/parser/parse_target.c | 3 + src/backend/utils/adt/json.c | 187 ++++++++- src/backend/utils/adt/jsonb.c | 175 +++++++- src/backend/utils/adt/ruleutils.c | 100 ++++- src/include/catalog/pg_proc.dat | 10 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 7 + src/include/nodes/primnodes.h | 4 + src/test/regress/expected/sqljson.out | 284 +++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/sqljson.sql | 88 ++++ 20 files changed, 1494 insertions(+), 18 deletions(-) create mode 100644 src/test/regress/expected/sqljson.out create mode 100644 src/test/regress/sql/sqljson.sql diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 312d70e469..9ebde14ebd 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2890,6 +2890,27 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) JumbleExpr(jstate, (Node *) conf->exclRelTlist); } break; + case T_JsonValueExpr: + { + JsonValueExpr *expr = (JsonValueExpr *) node; + + JumbleExpr(jstate, (Node *) expr->expr); + APP_JUMB(expr->format.type); + APP_JUMB(expr->format.encoding); + } + break; + case T_JsonCtorOpts: + { + JsonCtorOpts *opts = (JsonCtorOpts *) node; + + APP_JUMB(opts->returning.format.type); + APP_JUMB(opts->returning.format.encoding); + APP_JUMB(opts->returning.typid); + APP_JUMB(opts->returning.typmod); + APP_JUMB(opts->unique); + APP_JUMB(opts->absent_on_null); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index e4a6c20ed0..446f99416b 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2106,6 +2106,11 @@ ExecInitExprRec(Expr *node, ExprState *state, break; } + case T_JsonValueExpr: + ExecInitExprRec(((JsonValueExpr *) node)->expr, state, resv, + resnull); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 73bc02a5c5..9d3e39e0fe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2207,6 +2207,84 @@ _copyOnConflictExpr(const OnConflictExpr *from) return newnode; } +/* + * _copyJsonValueExpr + */ +static JsonValueExpr * +_copyJsonValueExpr(const JsonValueExpr *from) +{ + JsonValueExpr *newnode = makeNode(JsonValueExpr); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(format); + + return newnode; +} + +/* + * _copyJsonKeyValue + */ +static JsonKeyValue * +_copyJsonKeyValue(const JsonKeyValue *from) +{ + JsonKeyValue *newnode = makeNode(JsonKeyValue); + + COPY_NODE_FIELD(key); + COPY_NODE_FIELD(value); + + return newnode; +} + +/* + * _copyJsonObjectCtor + */ +static JsonObjectCtor * +_copyJsonObjectCtor(const JsonObjectCtor *from) +{ + JsonObjectCtor *newnode = makeNode(JsonObjectCtor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + COPY_LOCATION_FIELD(location); + + return newnode; +} + +/* + * _copyJsonCtorOpts + */ +static JsonCtorOpts * +_copyJsonCtorOpts(const JsonCtorOpts *from) +{ + JsonCtorOpts *newnode = makeNode(JsonCtorOpts); + + COPY_SCALAR_FIELD(returning.format.type); + COPY_SCALAR_FIELD(returning.format.encoding); + COPY_LOCATION_FIELD(returning.format.location); + COPY_SCALAR_FIELD(returning.typid); + COPY_SCALAR_FIELD(returning.typmod); + COPY_SCALAR_FIELD(unique); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + +/* + * _copyJsonOutput + */ +static JsonOutput * +_copyJsonOutput(const JsonOutput *from) +{ + JsonOutput *newnode = makeNode(JsonOutput); + + COPY_NODE_FIELD(typename); + COPY_SCALAR_FIELD(returning); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5108,6 +5186,21 @@ copyObjectImpl(const void *from) case T_OnConflictExpr: retval = _copyOnConflictExpr(from); break; + case T_JsonValueExpr: + retval = _copyJsonValueExpr(from); + break; + case T_JsonKeyValue: + retval = _copyJsonKeyValue(from); + break; + case T_JsonCtorOpts: + retval = _copyJsonCtorOpts(from); + break; + case T_JsonObjectCtor: + retval = _copyJsonObjectCtor(from); + break; + case T_JsonOutput: + retval = _copyJsonOutput(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 0d1cb840d7..70f123433f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -820,6 +820,31 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b) return true; } +static bool +_equalJsonValueExpr(const JsonValueExpr *a, const JsonValueExpr *b) +{ + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(format.type); + COMPARE_SCALAR_FIELD(format.encoding); + COMPARE_LOCATION_FIELD(format.location); + + return true; +} + +static bool +_equalJsonCtorOpts(const JsonCtorOpts *a, const JsonCtorOpts *b) +{ + COMPARE_SCALAR_FIELD(returning.format.type); + COMPARE_SCALAR_FIELD(returning.format.encoding); + COMPARE_LOCATION_FIELD(returning.format.location); + COMPARE_SCALAR_FIELD(returning.typid); + COMPARE_SCALAR_FIELD(returning.typmod); + COMPARE_SCALAR_FIELD(absent_on_null); + COMPARE_SCALAR_FIELD(unique); + + return true; +} + /* * Stuff from pathnodes.h */ @@ -3181,6 +3206,12 @@ equal(const void *a, const void *b) case T_JoinExpr: retval = _equalJoinExpr(a, b); break; + case T_JsonValueExpr: + retval = _equalJsonValueExpr(a, b); + break; + case T_JsonCtorOpts: + retval = _equalJsonCtorOpts(a, b); + break; /* * RELATION NODES diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 05ae73f7db..cf8e529b3b 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -259,6 +259,9 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + type = exprType((Node *) ((const JsonValueExpr *) expr)->expr); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -492,6 +495,8 @@ 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)->expr); default: break; } @@ -907,6 +912,9 @@ exprCollation(const Node *expr) case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_JsonValueExpr: + coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->expr); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1110,6 +1118,10 @@ exprSetCollation(Node *expr, Oid collation) Assert(!OidIsValid(collation)); /* result is always an integer * type */ break; + case T_JsonValueExpr: + exprSetCollation((Node *) ((const JsonValueExpr *) expr)->expr, + collation); + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1550,6 +1562,9 @@ exprLocation(const Node *expr) case T_PartitionRangeDatum: loc = ((const PartitionRangeDatum *) expr)->location; break; + case T_JsonValueExpr: + loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->expr); + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2247,6 +2262,8 @@ expression_tree_walker(Node *node, return true; } break; + case T_JsonValueExpr: + return walker(((JsonValueExpr *) node)->expr, context); default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3099,6 +3116,16 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + JsonValueExpr *newnode; + + FLATCOPY(newnode, jve, JsonValueExpr); + MUTATE(newnode->expr, jve->expr, Expr *); + + return (Node *) newnode; + } default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3744,6 +3771,30 @@ raw_expression_tree_walker(Node *node, break; case T_CommonTableExpr: return walker(((CommonTableExpr *) node)->ctequery, context); + case T_JsonValueExpr: + return walker(((JsonValueExpr *) node)->expr, context); + case T_JsonOutput: + return walker(((JsonOutput *) node)->typeName, context); + case T_JsonKeyValue: + { + JsonKeyValue *jkv = (JsonKeyValue *) node; + + if (walker(jkv->key, context)) + return true; + if (walker(jkv->value, context)) + return true; + } + break; + case T_JsonObjectCtor: + { + JsonObjectCtor *joc = (JsonObjectCtor *) node; + + if (walker(joc->output, context)) + return true; + if (walker(joc->exprs, 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 2be2cfac8b..13e8404662 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1686,6 +1686,31 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node) WRITE_NODE_FIELD(exclRelTlist); } +static void +_outJsonValueExpr(StringInfo str, const JsonValueExpr *node) +{ + WRITE_NODE_TYPE("JSONVALUEEXPR"); + + WRITE_NODE_FIELD(expr); + WRITE_ENUM_FIELD(format.type, JsonFormatType); + WRITE_ENUM_FIELD(format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(format.location); +} + +static void +_outJsonCtorOpts(StringInfo str, const JsonCtorOpts *node) +{ + WRITE_NODE_TYPE("JSONCTOROPTS"); + + WRITE_ENUM_FIELD(returning.format.type, JsonFormatType); + WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(returning.format.location); + WRITE_OID_FIELD(returning.typid); + WRITE_INT_FIELD(returning.typmod); + WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(absent_on_null); +} + /***************************************************************************** * * Stuff from pathnodes.h. @@ -4280,6 +4305,12 @@ outNode(StringInfo str, const void *obj) case T_PartitionRangeDatum: _outPartitionRangeDatum(str, obj); break; + case T_JsonValueExpr: + _outJsonValueExpr(str, obj); + break; + case T_JsonCtorOpts: + _outJsonCtorOpts(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 6ca26af9ec..3acc4c85b4 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1348,6 +1348,41 @@ _readOnConflictExpr(void) READ_DONE(); } +/* + * _readJsonValueExpr + */ +static JsonValueExpr * +_readJsonValueExpr(void) +{ + READ_LOCALS(JsonValueExpr); + + READ_NODE_FIELD(expr); + READ_ENUM_FIELD(format.type, JsonFormatType); + READ_ENUM_FIELD(format.encoding, JsonEncoding); + READ_LOCATION_FIELD(format.location); + + READ_DONE(); +} + +/* + * _readJsonCtorOpts + */ +static JsonCtorOpts * +_readJsonCtorOpts(void) +{ + READ_LOCALS(JsonCtorOpts); + + READ_ENUM_FIELD(returning.format.type, JsonFormatType); + READ_ENUM_FIELD(returning.format.encoding, JsonEncoding); + READ_LOCATION_FIELD(returning.format.location); + READ_OID_FIELD(returning.typid); + READ_INT_FIELD(returning.typmod); + READ_BOOL_FIELD(unique); + READ_BOOL_FIELD(absent_on_null); + + READ_DONE(); +} + /* * Stuff from parsenodes.h. */ @@ -2811,6 +2846,10 @@ parseNodeString(void) return_value = _readPartitionBoundSpec(); else if (MATCH("PARTITIONRANGEDATUM", 19)) return_value = _readPartitionRangeDatum(); + else if (MATCH("JSONVALUEEXPR", 13)) + return_value = _readJsonValueExpr(); + else if (MATCH("JSONCTOROPTS", 12)) + return_value = _readJsonCtorOpts(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 97f535a2f0..7ba32eb3f3 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -34,6 +34,7 @@ #include "parser/parse_agg.h" #include "utils/builtins.h" #include "utils/date.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -120,6 +121,7 @@ static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor); 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, @@ -368,6 +370,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_JsonObjectCtor: + result = transformJsonObjectCtor(pstate, (JsonObjectCtor *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3573,3 +3579,376 @@ 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->type == 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: + default: + enc = "UTF8"; + 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_PG_CONVERT_FROM, TEXTOID, + list_make2(expr, encoding), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + + fexpr->location = location; + + return (Node *) fexpr; +} + +/* + * Transform JSON value expression using specified input JSON format or + * default format otherwise. + */ +static Node * +transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, + JsonFormatType default_format) +{ + Node *expr = transformExprRecurse(pstate, (Node *) ve->expr); + 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"); + + exprtype = exprType(expr); + location = exprLocation(expr); + + get_type_category_preferred(exprtype, &typcategory, &typispreferred); + + if (ve->format.type != 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"))); + } + else + format = ve->format.type; + } + 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) + { + Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + Node *coerced; + FuncExpr *fexpr; + + if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg(ve->format.type == 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))); + + /* Convert encoded JSON text from bytea. */ + if (format == JS_FORMAT_JSON && exprtype == BYTEAOID) + { + expr = makeJsonByteaToTextConversion(expr, &ve->format, location); + exprtype = TEXTOID; + } + + /* Try to coerce to the target type. */ + coerced = coerce_to_target_type(pstate, expr, exprtype, + targettype, -1, + COERCION_EXPLICIT, + COERCE_INTERNAL_CAST, + location); + + if (coerced) + expr = coerced; + else + { + + /* If coercion failed, use to_json()/to_jsonb() functions. */ + fexpr = makeFuncExpr(targettype == JSONOID ? F_TO_JSON : F_TO_JSONB, + targettype, list_make1(expr), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + fexpr->location = location; + + expr = (Node *) fexpr; + } + + ve = copyObject(ve); + ve->expr = (Expr *) expr; + + expr = (Node *) ve; + } + + return expr; +} + +/* + * 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->type != 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->type == 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 void +transformJsonOutput(ParseState *pstate, const JsonOutput *output, + bool allow_format, JsonReturning *ret) +{ + /* if output clause is not specified, make default clause value */ + if (!output) + { + ret->format.type = JS_FORMAT_DEFAULT; + ret->format.encoding = JS_ENC_DEFAULT; + ret->format.location = -1; + ret->typid = InvalidOid; + ret->typmod = -1; + + return; + } + + *ret = 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.type == JS_FORMAT_DEFAULT) + /* assign JSONB format when returning jsonb, or JSON format otherwise */ + ret->format.type = + ret->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON; + else + checkJsonOutputFormat(pstate, &ret->format, ret->typid, allow_format); +} + +/* + * Coerce json[b]-valued function expression to the output type. + */ +static Node * +coerceJsonFuncExpr(ParseState *pstate, Node *expr, 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.type == 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_PG_CONVERT_TO, BYTEAOID, + list_make2(texpr, enc), + InvalidOid, InvalidOid, + COERCE_INTERNAL_CAST); + 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_INTERNAL_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 JsonCtorOpts * +makeJsonCtorOpts(const JsonReturning *returning, bool unique, + bool absent_on_null) +{ + JsonCtorOpts *opts = makeNode(JsonCtorOpts); + + opts->returning = *returning; + opts->unique = unique; + opts->absent_on_null = absent_on_null; + + return opts; +} + +/* + * 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 * +transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) +{ + JsonReturning returning; + FuncExpr *fexpr; + List *args = NIL; + Oid funcid; + Oid funcrettype; + + /* transform key-value pairs, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* append the first two arguments */ + args = lappend(args, makeBoolConst(ctor->absent_on_null, false)); + args = lappend(args, makeBoolConst(ctor->unique, false)); + + /* 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 = transformJsonValueExpr(pstate, kv->value, + JS_FORMAT_DEFAULT); + + args = lappend(args, key); + args = lappend(args, val); + } + } + + transformJsonOutput(pstate, ctor->output, true, &returning); + + if (returning.format.type == JS_FORMAT_JSONB) + { + funcid = args ? F_JSONB_BUILD_OBJECT_EXT : F_JSONB_BUILD_OBJECT_NOARGS; + funcrettype = JSONBOID; + } + else + { + funcid = args ? F_JSON_BUILD_OBJECT_EXT : F_JSON_BUILD_OBJECT_NOARGS; + funcrettype = JSONOID; + } + + fexpr = makeFuncExpr(funcid, funcrettype, args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = ctor->location; + fexpr->funcformat2 = FUNCFMT_JSON_OBJECT; + fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning, + ctor->unique, + ctor->absent_on_null); + + return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index ba470366e1..c45b24bd75 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1926,6 +1926,9 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_JsonObjectCtor: + *name = "json_object"; + return 2; default: break; } diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index b72738e624..0446c54406 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -66,6 +66,23 @@ typedef enum /* type categories for datum_to_json */ JSONTYPE_OTHER /* all else */ } JsonTypeCategory; +/* Context for key uniqueness check */ +typedef struct JsonUniqueCheckContext +{ + struct JsonKeyInfo + { + int offset; /* key offset: + * in result if positive, + * in skipped_keys if negative */ + int length; /* key length */ + } *keys; /* key info array */ + int nkeys; /* number of processed keys */ + int nallocated; /* number of allocated keys in array */ + StringInfo result; /* resulting json */ + StringInfoData skipped_keys; /* skipped keys with NULL values */ + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonUniqueCheckContext; + typedef struct JsonAggState { StringInfo str; @@ -2064,6 +2081,100 @@ json_agg_finalfn(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, "]")); } +static inline void +json_unique_check_init(JsonUniqueCheckContext *cxt, + StringInfo result, int nkeys) +{ + cxt->mcxt = CurrentMemoryContext; + cxt->nkeys = 0; + cxt->nallocated = nkeys ? nkeys : 16; + cxt->keys = palloc(sizeof(*cxt->keys) * cxt->nallocated); + cxt->result = result; + cxt->skipped_keys.data = NULL; +} + +static inline void +json_unique_check_free(JsonUniqueCheckContext *cxt) +{ + if (cxt->keys) + pfree(cxt->keys); + + if (cxt->skipped_keys.data) + pfree(cxt->skipped_keys.data); +} + +/* On-demand initialization of skipped_keys StringInfo structure */ +static inline StringInfo +json_unique_check_get_skipped_keys(JsonUniqueCheckContext *cxt) +{ + StringInfo out = &cxt->skipped_keys; + + if (!out->data) + { + MemoryContext oldcxt = MemoryContextSwitchTo(cxt->mcxt); + initStringInfo(out); + MemoryContextSwitchTo(oldcxt); + } + + return out; +} + +/* + * Save current key offset (key is not yet appended) to the key list, key + * length is saved later in json_unique_check_key() when the key is appended. + */ +static inline void +json_unique_check_save_key_offset(JsonUniqueCheckContext *cxt, StringInfo out) +{ + if (cxt->nkeys >= cxt->nallocated) + { + cxt->nallocated *= 2; + cxt->keys = repalloc(cxt->keys, sizeof(*cxt->keys) * cxt->nallocated); + } + + cxt->keys[cxt->nkeys++].offset = out->len; +} + +/* + * Check uniqueness of key already appended to 'out' StringInfo. + */ +static inline void +json_unique_check_key(JsonUniqueCheckContext *cxt, StringInfo out) +{ + struct JsonKeyInfo *keys = cxt->keys; + int curr = cxt->nkeys - 1; + int offset = keys[curr].offset; + int length = out->len - offset; + char *curr_key = &out->data[offset]; + int i; + + keys[curr].length = length; /* save current key length */ + + if (out == &cxt->skipped_keys) + /* invert offset for skipped keys for their recognition */ + keys[curr].offset = -keys[curr].offset; + + /* check collisions with previous keys */ + for (i = 0; i < curr; i++) + { + char *prev_key; + + if (cxt->keys[i].length != length) + continue; + + offset = cxt->keys[i].offset; + + prev_key = offset > 0 + ? &cxt->result->data[offset] + : &cxt->skipped_keys.data[-offset]; + + if (!memcmp(curr_key, prev_key, length)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key %s", curr_key))); + } +} + /* * json_object_agg transition function. * @@ -2198,11 +2309,9 @@ catenate_stringinfo_string(StringInfo buffer, const char *addon) return result; } -/* - * SQL function json_build_object(variadic "any") - */ -Datum -json_build_object(PG_FUNCTION_ARGS) +static Datum +json_build_object_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null, bool unique_keys) { int nargs = PG_NARGS(); int i; @@ -2211,9 +2320,11 @@ json_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + JsonUniqueCheckContext unique_check; /* fetch argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, false, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -2230,19 +2341,53 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '{'); + if (unique_keys) + json_unique_check_init(&unique_check, result, nargs / 2); + for (i = 0; i < nargs; i += 2) { - appendStringInfoString(result, sep); - sep = ", "; + StringInfo out; + bool skip; + + /* 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_check_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", first_vararg + i + 1), errhint("Object keys should be text."))); - add_json(args[i], false, result, types[i], true); + if (unique_keys) + /* save key offset before key appending */ + json_unique_check_save_key_offset(&unique_check, out); + + add_json(args[i], false, out, types[i], true); + + if (unique_keys) + { + /* check key uniqueness after key appending */ + json_unique_check_key(&unique_check, out); + + if (skip) + continue; + } appendStringInfoString(result, " : "); @@ -2252,9 +2397,31 @@ json_build_object(PG_FUNCTION_ARGS) appendStringInfoChar(result, '}'); + if (unique_keys) + json_unique_check_free(&unique_check); + PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +/* + * SQL function json_build_object(variadic "any") + */ +Datum +json_build_object(PG_FUNCTION_ARGS) +{ + return json_build_object_worker(fcinfo, 0, false, false); +} + +/* + * SQL function json_build_object_ext(absent_on_null bool, unique bool, variadic "any") + */ +Datum +json_build_object_ext(PG_FUNCTION_ARGS) +{ + return json_build_object_worker(fcinfo, 2, + PG_GETARG_BOOL(0), PG_GETARG_BOOL(1)); +} + /* * degenerate case of json_build_object where it gets 0 arguments. */ diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index dc00a7c8b4..79c9fe744f 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -52,6 +52,16 @@ typedef enum /* type categories for datum_to_jsonb */ JSONBTYPE_OTHER /* all else */ } JsonbTypeCategory; +/* Context for key uniqueness check */ +typedef struct JsonbUniqueCheckContext +{ + JsonbValue *obj; /* object containing skipped keys also */ + int *skipped_keys; /* array of skipped key-value pair indices */ + int skipped_keys_allocated; + int skipped_keys_count; + MemoryContext mcxt; /* context for saving skipped keys */ +} JsonbUniqueCheckContext; + typedef struct JsonbAggState { JsonbInState *res; @@ -1135,11 +1145,121 @@ to_jsonb(PG_FUNCTION_ARGS) PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } +static inline void +jsonb_unique_check_init(JsonbUniqueCheckContext *cxt, JsonbValue *obj, + MemoryContext mcxt) +{ + cxt->mcxt = mcxt; + cxt->obj = obj; + cxt->skipped_keys = NULL; + cxt->skipped_keys_count = 0; + cxt->skipped_keys_allocated = 0; +} + /* - * SQL function jsonb_build_object(variadic "any") + * Save the index of the skipped key-value pair that has just been appended + * to the object. */ -Datum -jsonb_build_object(PG_FUNCTION_ARGS) +static inline void +jsonb_unique_check_add_skipped(JsonbUniqueCheckContext *cxt) +{ + /* + * Make a room for the skipped index plus one additional index + * (see jsonb_unique_check_remove_skipped_keys()). + */ + if (cxt->skipped_keys_count + 1 >= cxt->skipped_keys_allocated) + { + if (cxt->skipped_keys_allocated) + { + cxt->skipped_keys_allocated *= 2; + cxt->skipped_keys = repalloc(cxt->skipped_keys, + sizeof(*cxt->skipped_keys) * + cxt->skipped_keys_allocated); + } + else + { + cxt->skipped_keys_allocated = 16; + cxt->skipped_keys = MemoryContextAlloc(cxt->mcxt, + sizeof(*cxt->skipped_keys) * + cxt->skipped_keys_allocated); + } + } + + cxt->skipped_keys[cxt->skipped_keys_count++] = cxt->obj->val.object.nPairs; +} + +/* + * Check uniqueness of the key that has just been appended to the object. + */ +static inline void +jsonb_unique_check_key(JsonbUniqueCheckContext *cxt, bool skip) +{ + JsonbPair *pair = cxt->obj->val.object.pairs; + /* nPairs is incremented only after the value is appended */ + JsonbPair *last = &pair[cxt->obj->val.object.nPairs]; + + for (; pair < last; pair++) + if (pair->key.val.string.len == + last->key.val.string.len && + !memcmp(pair->key.val.string.val, + last->key.val.string.val, + last->key.val.string.len)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE), + errmsg("duplicate JSON key \"%*s\"", + last->key.val.string.len, + last->key.val.string.val))); + + if (skip) + { + /* save skipped key index */ + jsonb_unique_check_add_skipped(cxt); + + /* add dummy null value for the skipped key */ + last->value.type = jbvNull; + cxt->obj->val.object.nPairs++; + } +} + +/* + * Remove skipped key-value pairs from the resulting object. + */ +static void +jsonb_unique_check_remove_skipped_keys(JsonbUniqueCheckContext *cxt) +{ + int *skipped_keys = cxt->skipped_keys; + int skipped_keys_count = cxt->skipped_keys_count; + + if (!skipped_keys_count) + return; + + if (cxt->obj->val.object.nPairs > skipped_keys_count) + { + /* remove skipped key-value pairs */ + JsonbPair *pairs = cxt->obj->val.object.pairs; + int i; + + /* save total pair count into the last element of skipped_keys */ + Assert(cxt->skipped_keys_count < cxt->skipped_keys_allocated); + cxt->skipped_keys[cxt->skipped_keys_count] = cxt->obj->val.object.nPairs; + + for (i = 0; i < skipped_keys_count; i++) + { + int skipped_key = skipped_keys[i]; + int nkeys = skipped_keys[i + 1] - skipped_key - 1; + + memmove(&pairs[skipped_key - i], + &pairs[skipped_key + 1], + sizeof(JsonbPair) * nkeys); + } + } + + cxt->obj->val.object.nPairs -= skipped_keys_count; +} + +static Datum +jsonb_build_object_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null, bool unique_keys) { int nargs; int i; @@ -1147,9 +1267,11 @@ jsonb_build_object(PG_FUNCTION_ARGS) Datum *args; bool *nulls; Oid *types; + JsonbUniqueCheckContext unique_check; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -1166,25 +1288,68 @@ jsonb_build_object(PG_FUNCTION_ARGS) result.res = pushJsonbValue(&result.parseState, WJB_BEGIN_OBJECT, NULL); + /* if (unique_keys) */ + jsonb_unique_check_init(&unique_check, result.res, CurrentMemoryContext); + 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))); + errmsg("argument %d: key must not be null", + first_vararg + 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); + if (unique_keys) + { + jsonb_unique_check_key(&unique_check, skip); + + if (skip) + continue; /* do not process the value if the key is skipped */ + } + /* process value */ add_jsonb(args[i + 1], nulls[i + 1], &result, types[i + 1], false); } + if (unique_keys && absent_on_null) + jsonb_unique_check_remove_skipped_keys(&unique_check); + result.res = pushJsonbValue(&result.parseState, WJB_END_OBJECT, NULL); PG_RETURN_POINTER(JsonbValueToJsonb(result.res)); } +/* + * SQL function jsonb_build_object(variadic "any") + */ +Datum +jsonb_build_object(PG_FUNCTION_ARGS) +{ + return jsonb_build_object_worker(fcinfo, 0, false, false); +} + +/* + * SQL function jsonb_build_object_ext(absent_on_null bool, unique bool, variadic "any") + */ +Datum +jsonb_build_object_ext(PG_FUNCTION_ARGS) +{ + return jsonb_build_object_worker(fcinfo, 2, + PG_GETARG_BOOL(0), PG_GETARG_BOOL(1)); +} + /* * degenerate case of jsonb_build_object where it gets 0 arguments. */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index d741c02d8b..079d2457c5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7817,6 +7817,48 @@ get_rule_expr_paren(Node *node, deparse_context *context, appendStringInfoChar(context->buf, ')'); } +/* + * get_json_format - Parse back a JsonFormat structure + */ +static void +get_json_format(JsonFormat *format, deparse_context *context) +{ + if (format->type == JS_FORMAT_DEFAULT) + return; + + appendStringInfoString(context->buf, + format->type == 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(context->buf, " ENCODING %s", encoding); + } +} + +/* + * get_json_returning - Parse back a JsonReturning structure + */ +static void +get_json_returning(JsonReturning *returning, deparse_context *context, + bool json_format_by_default) +{ + if (!OidIsValid(returning->typid)) + return; + + appendStringInfo(context->buf, " RETURNING %s", + format_type_with_typemod(returning->typid, + returning->typmod)); + + if (!json_format_by_default || + returning->format.type != + (returning->typid == JSONBOID ? JS_FORMAT_JSONB : JS_FORMAT_JSON)) + get_json_format(&returning->format, context); +} /* * get_coercion - Parse back a coercion @@ -8953,6 +8995,15 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_JsonValueExpr: + { + JsonValueExpr *jve = (JsonValueExpr *) node; + + get_rule_expr((Node *) jve->expr, context, false); + get_json_format(&jve->format, context); + } + break; + case T_List: { char *sep; @@ -9124,6 +9175,35 @@ get_func_opts(FuncFormat aggformat, Node *aggformatopts, deparse_context *contex { switch (aggformat) { + case FUNCFMT_JSON_OBJECT: + case FUNCFMT_JSON_OBJECTAGG: + case FUNCFMT_JSON_ARRAY: + case FUNCFMT_JSON_ARRAYAGG: + { + JsonCtorOpts *opts = castNode(JsonCtorOpts, aggformatopts); + + if (!opts) + break; + + if (opts->absent_on_null) + { + if (aggformat == FUNCFMT_JSON_OBJECT || + aggformat == FUNCFMT_JSON_OBJECTAGG) + appendStringInfoString(context->buf, " ABSENT ON NULL"); + } + else + { + if (aggformat == FUNCFMT_JSON_ARRAY || + aggformat == FUNCFMT_JSON_ARRAYAGG) + appendStringInfoString(context->buf, " NULL ON NULL"); + } + + if (opts->unique) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + + get_json_returning(&opts->returning, context, true); + } + break; default: break; } @@ -9140,6 +9220,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context, Oid funcoid = expr->funcid; Oid argtypes[FUNC_MAX_ARGS]; int nargs; + int firstarg; List *argnames; bool use_variadic; ListCell *l; @@ -9200,12 +9281,18 @@ get_func_expr(FuncExpr *expr, deparse_context *context, switch (expr->funcformat2) { + case FUNCFMT_JSON_OBJECT: + funcname = "JSON_OBJECT"; + firstarg = 2; + use_variadic = false; + break; default: funcname = generate_function_name(funcoid, nargs, argnames, argtypes, expr->funcvariadic, &use_variadic, context->special_exprkind); + firstarg = 0; break; } @@ -9214,8 +9301,17 @@ get_func_expr(FuncExpr *expr, deparse_context *context, nargs = 0; foreach(l, expr->args) { - if (nargs++ > 0) - appendStringInfoString(buf, ", "); + if (nargs++ < firstarg) + continue; + + if (nargs > firstarg + 1) + { + const char *sep = expr->funcformat2 == FUNCFMT_JSON_OBJECT && + !((nargs - firstarg) % 2) ? " : " : ", "; + + appendStringInfoString(buf, sep); + } + if (use_variadic && lnext(l) == NULL) appendStringInfoString(buf, "VARIADIC "); get_rule_expr((Node *) lfirst(l), context, true); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index f9f3837b04..66293c7fca 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8212,6 +8212,11 @@ proname => 'json_build_object', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => '', prosrc => 'json_build_object_noargs' }, +{ oid => '6066', descr => 'build a json object from pairwise key/value inputs', + proname => 'json_build_object_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'bool bool any', + proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}', + prosrc => 'json_build_object_ext' }, { oid => '3202', descr => 'map text array of key value pairs to json object', proname => 'json_object', prorettype => 'json', proargtypes => '_text', prosrc => 'json_object' }, @@ -9066,6 +9071,11 @@ proname => 'jsonb_build_object', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => '', prosrc => 'jsonb_build_object_noargs' }, +{ oid => '6067', descr => 'build a jsonb object from pairwise key/value inputs', + proname => 'jsonb_build_object_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool bool any', + proallargtypes => '{bool,bool,any}', proargmodes => '{i,i,v}', + prosrc => 'jsonb_build_object_ext' }, { oid => '3262', descr => 'remove object fields with null values from jsonb', proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb', prosrc => 'jsonb_strip_nulls' }, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 9461f5dfd9..d8e85ef412 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -490,6 +490,7 @@ typedef enum NodeTag T_JsonKeyValue, T_JsonBehavior, T_JsonOutput, + T_JsonCtorOpts, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 6cbadf150f..24dd036f8d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1663,6 +1663,13 @@ typedef struct JsonArrayAgg bool absent_on_null; /* skip NULL elements? */ } JsonArrayAgg; +typedef struct JsonCtorOpts +{ + NodeTag type; + JsonReturning returning; /* RETURNING clause */ + bool absent_on_null; /* skip NULL values? */ + bool unique; /* check key uniqueness? */ +} JsonCtorOpts; /***************************************************************************** * Raw Grammar Output Statements diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index f27c5b30d9..6b57b7893f 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -256,6 +256,10 @@ typedef struct Param typedef enum FuncFormat { FUNCFMT_REGULAR = 0, + FUNCFMT_JSON_OBJECT = 1, + FUNCFMT_JSON_ARRAY = 2, + FUNCFMT_JSON_OBJECTAGG = 3, + FUNCFMT_JSON_ARRAYAGG = 4 } FuncFormat; /* diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out new file mode 100644 index 0000000000..f7d92456d6 --- /dev/null +++ b/src/test/regress/expected/sqljson.out @@ -0,0 +1,284 @@ +-- 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 + 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 + 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 3 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", "1.23" : 123, "c" : [ 1,true,{ } ], "d" : {"x": 123.45}} +(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 key "1" +SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE RETURNING jsonb); +ERROR: duplicate JSON key "1" +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 key "1" +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) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 84e3ec6c91..ad3b3f1630 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -99,7 +99,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 json_jsonpath jsonb_jsonpath +test: json jsonb json_encoding jsonpath jsonpath_encoding json_jsonpath jsonb_jsonpath sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 3f363e476c..b814dd9b0b 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -167,6 +167,7 @@ test: jsonpath test: jsonpath_encoding test: json_jsonpath test: jsonb_jsonpath +test: sqljson test: plancache test: limit test: plpgsql diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql new file mode 100644 index 0000000000..13ad4d3429 --- /dev/null +++ b/src/test/regress/sql/sqljson.sql @@ -0,0 +1,88 @@ +-- 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); + From 621d6a4c29506fdf556a51c2f5e33ac9e317007c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 12:10:04 +0300 Subject: [PATCH 24/66] Add JSON_ARRAY() transformation --- src/backend/nodes/copyfuncs.c | 19 ++++ src/backend/nodes/nodeFuncs.c | 10 ++ src/backend/parser/parse_expr.c | 65 ++++++++++++ src/backend/parser/parse_target.c | 3 + src/backend/utils/adt/json.c | 32 ++++-- src/backend/utils/adt/jsonb.c | 34 +++++-- src/backend/utils/adt/ruleutils.c | 7 ++ src/include/catalog/pg_proc.dat | 10 ++ src/test/regress/expected/sqljson.out | 137 ++++++++++++++++++++++++++ src/test/regress/sql/sqljson.sql | 29 ++++++ 10 files changed, 334 insertions(+), 12 deletions(-) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9d3e39e0fe..f25635cf3b 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2285,6 +2285,22 @@ _copyJsonOutput(const JsonOutput *from) return newnode; } +/* + * _copyJsonArrayCtor + */ +static JsonArrayCtor * +_copyJsonArrayCtor(const JsonArrayCtor *from) +{ + JsonArrayCtor *newnode = makeNode(JsonArrayCtor); + + COPY_NODE_FIELD(exprs); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5201,6 +5217,9 @@ copyObjectImpl(const void *from) case T_JsonOutput: retval = _copyJsonOutput(from); break; + case T_JsonArrayCtor: + retval = _copyJsonArrayCtor(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index cf8e529b3b..fa622fe4e5 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3795,6 +3795,16 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonArrayCtor: + { + JsonArrayCtor *jac = (JsonArrayCtor *) node; + + if (walker(jac->output, context)) + return true; + if (walker(jac->exprs, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 7ba32eb3f3..662c081875 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -122,6 +122,7 @@ static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor); +static Node *transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor); 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, @@ -374,6 +375,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonObjectCtor(pstate, (JsonObjectCtor *) expr); break; + case T_JsonArrayCtor: + result = transformJsonArrayCtor(pstate, (JsonArrayCtor *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3952,3 +3957,63 @@ transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); } + +/* + * 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 * +transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor) +{ + JsonReturning returning; + FuncExpr *fexpr; + List *args = NIL; + Oid funcid; + Oid funcrettype; + + /* transform element expressions, if any */ + if (ctor->exprs) + { + ListCell *lc; + + /* append the first absent_on_null argument */ + args = lappend(args, makeBoolConst(ctor->absent_on_null, false)); + + /* transform and append element arguments */ + foreach(lc, ctor->exprs) + { + JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); + Node *val = transformJsonValueExpr(pstate, jsval, + JS_FORMAT_DEFAULT); + + args = lappend(args, val); + } + } + + transformJsonOutput(pstate, ctor->output, true, &returning); + + if (returning.format.type == JS_FORMAT_JSONB) + { + funcid = args ? F_JSONB_BUILD_ARRAY_EXT : F_JSONB_BUILD_ARRAY_NOARGS; + funcrettype = JSONBOID; + } + else + { + funcid = args ? F_JSON_BUILD_ARRAY_EXT : F_JSON_BUILD_ARRAY_NOARGS; + funcrettype = JSONOID; + } + + fexpr = makeFuncExpr(funcid, funcrettype, args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = ctor->location; + fexpr->funcformat2 = FUNCFMT_JSON_ARRAY; + fexpr->funcformatopts = (Node *) makeJsonCtorOpts(&returning, false, + ctor->absent_on_null); + + return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index c45b24bd75..e6e9d461ea 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1929,6 +1929,9 @@ FigureColnameInternal(Node *node, char **name) case T_JsonObjectCtor: *name = "json_object"; return 2; + case T_JsonArrayCtor: + *name = "json_array"; + return 2; default: break; } diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 0446c54406..445dc4bded 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -2431,11 +2431,9 @@ 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) +static Datum +json_build_array_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null) { int nargs; int i; @@ -2446,7 +2444,8 @@ json_build_array(PG_FUNCTION_ARGS) Oid *types; /* fetch argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, false, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -2457,6 +2456,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); @@ -2467,6 +2469,24 @@ json_build_array(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text_with_len(result->data, result->len)); } +/* + * SQL function json_build_array(variadic "any") + */ +Datum +json_build_array(PG_FUNCTION_ARGS) +{ + return json_build_array_worker(fcinfo, 0, false); +} + +/* + * SQL function json_build_array_ext(absent_on_null bool, variadic "any") + */ +Datum +json_build_array_ext(PG_FUNCTION_ARGS) +{ + return json_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0)); +} + /* * degenerate case of json_build_array where it gets 0 arguments. */ diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 79c9fe744f..2a4189906f 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -1366,11 +1366,9 @@ 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) +static Datum +jsonb_build_array_worker(FunctionCallInfo fcinfo, int first_vararg, + bool absent_on_null) { int nargs; int i; @@ -1380,7 +1378,8 @@ jsonb_build_array(PG_FUNCTION_ARGS) Oid *types; /* build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = extract_variadic_args(fcinfo, first_vararg, true, + &args, &types, &nulls); if (nargs < 0) PG_RETURN_NULL(); @@ -1390,13 +1389,36 @@ jsonb_build_array(PG_FUNCTION_ARGS) 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)); } +/* + * SQL function jsonb_build_array(variadic "any") + */ +Datum +jsonb_build_array(PG_FUNCTION_ARGS) +{ + return jsonb_build_array_worker(fcinfo, 0, false); +} + +/* + * SQL function jsonb_build_array_ext(absent_on_null bool, variadic "any") + */ +Datum +jsonb_build_array_ext(PG_FUNCTION_ARGS) +{ + return jsonb_build_array_worker(fcinfo, 1, PG_GETARG_BOOL(0)); +} + /* * degenerate case of jsonb_build_array where it gets 0 arguments. */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 079d2457c5..43b8a0bc34 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9286,6 +9286,13 @@ get_func_expr(FuncExpr *expr, deparse_context *context, firstarg = 2; use_variadic = false; break; + + case FUNCFMT_JSON_ARRAY: + funcname = "JSON_ARRAY"; + firstarg = 1; + use_variadic = false; + break; + default: funcname = generate_function_name(funcoid, nargs, argnames, argtypes, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 66293c7fca..b0188ff0d0 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8202,6 +8202,11 @@ proname => 'json_build_array', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => '', prosrc => 'json_build_array_noargs' }, +{ oid => '3998', descr => 'build a json array from any inputs', + proname => 'json_build_array_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'bool any', + proallargtypes => '{bool,any}', proargmodes => '{i,v}', + prosrc => 'json_build_array_ext' }, { oid => '3200', descr => 'build a json object from pairwise key/value inputs', proname => 'json_build_object', provariadic => 'any', proisstrict => 'f', @@ -9061,6 +9066,11 @@ proname => 'jsonb_build_array', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => '', prosrc => 'jsonb_build_array_noargs' }, +{ oid => '6068', descr => 'build a jsonb array from any inputs', + proname => 'jsonb_build_array_ext', provariadic => 'any', proisstrict => 'f', + provolatile => 's', prorettype => 'jsonb', proargtypes => 'bool any', + proallargtypes => '{bool,any}', proargmodes => '{i,v}', + prosrc => 'jsonb_build_array_ext' }, { oid => '3273', descr => 'build a jsonb object from pairwise key/value inputs', proname => 'jsonb_build_object', provariadic => 'any', proisstrict => 'f', diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index f7d92456d6..b3eef1e310 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -282,3 +282,140 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WIT {"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) + diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 13ad4d3429..7d826660f3 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -86,3 +86,32 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '1': 1 ABSENT ON NULL WITH UNIQUE RETURNING 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); From 59bc00fcfa367498cd7cf31abd0b36ee22ad5cbb Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 23:29:46 +0300 Subject: [PATCH 25/66] Add JSON_OBJECTAGG() and JSON_ARRAYAGG() transformation --- src/backend/nodes/copyfuncs.c | 47 +++++- src/backend/nodes/nodeFuncs.c | 32 +++++ src/backend/parser/parse_expr.c | 174 +++++++++++++++++++++++ src/backend/parser/parse_target.c | 6 + src/backend/utils/adt/json.c | 99 +++++++++++-- src/backend/utils/adt/jsonb.c | 85 +++++++++-- src/backend/utils/adt/ruleutils.c | 72 ++++++++-- src/include/catalog/pg_aggregate.dat | 8 ++ src/include/catalog/pg_proc.dat | 32 +++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/expected/sqljson.out | 157 ++++++++++++++++++++ src/test/regress/sql/opr_sanity.sql | 6 +- src/test/regress/sql/sqljson.sql | 87 ++++++++++++ 13 files changed, 773 insertions(+), 38 deletions(-) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f25635cf3b..a63325dddd 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2271,6 +2271,26 @@ _copyJsonCtorOpts(const JsonCtorOpts *from) return newnode; } +/* + * _copyJsonObjectAgg + */ +static JsonObjectAgg * +_copyJsonObjectAgg(const JsonObjectAgg *from) +{ + JsonObjectAgg *newnode = makeNode(JsonObjectAgg); + + COPY_NODE_FIELD(ctor.output); + COPY_NODE_FIELD(ctor.agg_filter); + COPY_NODE_FIELD(ctor.agg_order); + COPY_NODE_FIELD(ctor.over); + COPY_LOCATION_FIELD(ctor.location); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + COPY_SCALAR_FIELD(unique); + + return newnode; +} + /* * _copyJsonOutput */ @@ -2279,7 +2299,7 @@ _copyJsonOutput(const JsonOutput *from) { JsonOutput *newnode = makeNode(JsonOutput); - COPY_NODE_FIELD(typename); + COPY_NODE_FIELD(typeName); COPY_SCALAR_FIELD(returning); return newnode; @@ -2301,6 +2321,25 @@ _copyJsonArrayCtor(const JsonArrayCtor *from) return newnode; } +/* + * _copyJsonArrayAgg + */ +static JsonArrayAgg * +_copyJsonArrayAgg(const JsonArrayAgg *from) +{ + JsonArrayAgg *newnode = makeNode(JsonArrayAgg); + + COPY_NODE_FIELD(ctor.output); + COPY_NODE_FIELD(ctor.agg_filter); + COPY_NODE_FIELD(ctor.agg_order); + COPY_NODE_FIELD(ctor.over); + COPY_LOCATION_FIELD(ctor.location); + COPY_NODE_FIELD(arg); + COPY_SCALAR_FIELD(absent_on_null); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5214,12 +5253,18 @@ copyObjectImpl(const void *from) case T_JsonObjectCtor: retval = _copyJsonObjectCtor(from); break; + case T_JsonObjectAgg: + retval = _copyJsonObjectAgg(from); + break; case T_JsonOutput: retval = _copyJsonOutput(from); break; case T_JsonArrayCtor: retval = _copyJsonArrayCtor(from); break; + case T_JsonArrayAgg: + retval = _copyJsonArrayAgg(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index fa622fe4e5..298852eb90 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3805,6 +3805,38 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonObjectAgg: + { + JsonObjectAgg *joa = (JsonObjectAgg *) node; + + if (walker(joa->ctor.output, context)) + return true; + if (walker(joa->ctor.agg_order, context)) + return true; + if (walker(joa->ctor.agg_filter, context)) + return true; + if (walker(joa->ctor.over, context)) + return true; + if (walker(joa->arg, context)) + return true; + } + break; + case T_JsonArrayAgg: + { + JsonArrayAgg *jaa = (JsonArrayAgg *) node; + + if (walker(jaa->ctor.output, context)) + return true; + if (walker(jaa->ctor.agg_order, context)) + return true; + if (walker(jaa->ctor.agg_filter, context)) + return true; + if (walker(jaa->ctor.over, context)) + return true; + if (walker(jaa->arg, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 662c081875..1c33fc74eb 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" @@ -123,6 +125,8 @@ static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor); static Node *transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor); +static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); +static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); 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, @@ -379,6 +383,14 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonArrayCtor(pstate, (JsonArrayCtor *) expr); break; + case T_JsonObjectAgg: + result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr); + break; + + case T_JsonArrayAgg: + result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3958,6 +3970,168 @@ transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); } +/* + * Common code for JSON_OBJECTAGG and JSON_ARRAYAGG transformation. + */ +static Node * +transformJsonAggCtor(ParseState *pstate, JsonAggCtor *agg_ctor, + JsonReturning *returning, List *args, const char *aggfn, + Oid aggtype, FuncFormat format, JsonCtorOpts *formatopts) +{ + 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->winformat = format; + wfunc->winformatopts = (Node *) formatopts; + 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->aggformat = format; + aggref->aggformatopts = (Node *) formatopts; + aggref->location = agg_ctor->location; + + transformAggregateCall(pstate, aggref, args, agg_ctor->agg_order, false); + + node = (Node *) aggref; + } + + return coerceJsonFuncExpr(pstate, node, returning, true); +} + +/* + * 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; + + transformJsonOutput(pstate, agg->ctor.output, true, &returning); + + key = transformExprRecurse(pstate, (Node *) agg->arg->key); + val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT); + + args = list_make4(key, + val, + makeBoolConst(agg->absent_on_null, false), + makeBoolConst(agg->unique, false)); + + if (returning.format.type == JS_FORMAT_JSONB) + { + aggfnname = "pg_catalog.jsonb_objectagg"; /* F_JSONB_OBJECTAGG */ + aggtype = JSONBOID; + } + else + { + aggfnname = "pg_catalog.json_objectagg"; /* F_JSON_OBJECTAGG; */ + aggtype = JSONOID; + } + + return transformJsonAggCtor(pstate, &agg->ctor, &returning, args, aggfnname, + aggtype, FUNCFMT_JSON_OBJECTAGG, + makeJsonCtorOpts(&returning, + 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; + + transformJsonOutput(pstate, agg->ctor.output, true, &returning); + + arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT); + + if (returning.format.type == 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 transformJsonAggCtor(pstate, &agg->ctor, &returning, list_make1(arg), + aggfnname, aggtype, FUNCFMT_JSON_ARRAYAGG, + makeJsonCtorOpts(&returning, + false, agg->absent_on_null)); +} + /* * Transform JSON_ARRAY() constructor. * diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index e6e9d461ea..4a89d816a7 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1932,6 +1932,12 @@ FigureColnameInternal(Node *node, char **name) case T_JsonArrayCtor: *name = "json_array"; return 2; + case T_JsonObjectAgg: + *name = "json_objectagg"; + return 2; + case T_JsonArrayAgg: + *name = "json_arrayagg"; + return 2; default: break; } diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 445dc4bded..4f7aabc648 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -90,6 +90,7 @@ typedef struct JsonAggState Oid key_output_func; JsonTypeCategory val_category; Oid val_output_func; + JsonUniqueCheckContext unique_check; } JsonAggState; static inline void json_lex(JsonLexContext *lex); @@ -1985,8 +1986,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; @@ -2026,9 +2027,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)) { @@ -2040,7 +2046,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)) { @@ -2058,6 +2064,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 */ @@ -2180,13 +2205,16 @@ json_unique_check_key(JsonUniqueCheckContext *cxt, StringInfo out) * * 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; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -2207,6 +2235,10 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) oldcontext = MemoryContextSwitchTo(aggcontext); state = (JsonAggState *) palloc(sizeof(JsonAggState)); state->str = makeStringInfo(); + if (unique_keys) + json_unique_check_init(&state->unique_check, state->str, 0); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -2234,7 +2266,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -2250,11 +2281,41 @@ 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_check_get_skipped_keys(&state->unique_check); + } + else + { + out = state->str; + + if (out->len > 2) + appendStringInfoString(out, ", "); + } + arg = PG_GETARG_DATUM(1); - datum_to_json(arg, false, state->str, state->key_category, + if (unique_keys) + json_unique_check_save_key_offset(&state->unique_check, out); + + datum_to_json(arg, false, out, state->key_category, state->key_output_func, true); + if (unique_keys) + { + json_unique_check_key(&state->unique_check, out); + + if (skip) + PG_RETURN_POINTER(state); + } + appendStringInfoString(state->str, " : "); if (PG_ARGISNULL(2)) @@ -2268,6 +2329,26 @@ 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_objectagg aggregate function + */ +Datum +json_objectagg_transfn(PG_FUNCTION_ARGS) +{ + return json_object_agg_transfn_worker(fcinfo, + PG_GETARG_BOOL(3), + PG_GETARG_BOOL(4)); +} + /* * json_object_agg final function. */ @@ -2285,6 +2366,8 @@ json_object_agg_finalfn(PG_FUNCTION_ARGS) if (state == NULL) PG_RETURN_NULL(); + json_unique_check_free(&state->unique_check); + /* Else return state with appropriate object terminator added */ PG_RETURN_TEXT_P(catenate_stringinfo_string(state->str, " }")); } diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 2a4189906f..dbb056ee1b 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -69,6 +69,7 @@ typedef struct JsonbAggState Oid key_output_func; JsonbTypeCategory val_category; Oid val_output_func; + JsonbUniqueCheckContext unique_check; } JsonbAggState; static inline Datum jsonb_from_cstring(char *json, int len); @@ -1670,12 +1671,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; @@ -1723,6 +1720,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); @@ -1792,6 +1792,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) { @@ -1824,11 +1842,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; @@ -1842,6 +1858,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1861,6 +1878,11 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) state->res = result; result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_OBJECT, NULL); + if (unique_keys) + jsonb_unique_check_init(&state->unique_check, result->res, + aggcontext); + else + memset(&state->unique_check, 0, sizeof(state->unique_check)); MemoryContextSwitchTo(oldcontext); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); @@ -1896,6 +1918,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)); @@ -1951,6 +1982,18 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) } result->res = pushJsonbValue(&result->parseState, WJB_KEY, &v); + + if (unique_keys) + { + jsonb_unique_check_key(&state->unique_check, skip); + + if (skip) + { + MemoryContextSwitchTo(oldcontext); + PG_RETURN_POINTER(state); + } + } + break; case WJB_END_ARRAY: break; @@ -2023,6 +2066,26 @@ 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_objectagg aggregate function + */ +Datum +jsonb_objectagg_transfn(PG_FUNCTION_ARGS) +{ + return jsonb_object_agg_transfn_worker(fcinfo, + PG_GETARG_BOOL(3), + PG_GETARG_BOOL(4)); +} + Datum jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) { diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 43b8a0bc34..8a923c481e 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9338,8 +9338,9 @@ get_agg_expr(Aggref *aggref, deparse_context *context, { StringInfo buf = context->buf; Oid argtypes[FUNC_MAX_ARGS]; + const char *funcname; int nargs; - bool use_variadic; + bool use_variadic = false; /* * For a combining aggregate, we look up and deparse the corresponding @@ -9368,13 +9369,24 @@ get_agg_expr(Aggref *aggref, deparse_context *context, /* Extract the argument types as seen by the parser */ nargs = get_aggregate_argtypes(aggref, argtypes); + switch (aggref->aggformat) + { + case FUNCFMT_JSON_OBJECTAGG: + funcname = "JSON_OBJECTAGG"; + break; + case FUNCFMT_JSON_ARRAYAGG: + funcname = "JSON_ARRAYAGG"; + break; + default: + funcname = generate_function_name(aggref->aggfnoid, nargs, NIL, + argtypes, aggref->aggvariadic, + &use_variadic, + context->special_exprkind); + break; + } + /* 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)) @@ -9410,7 +9422,17 @@ get_agg_expr(Aggref *aggref, deparse_context *context, if (tle->resjunk) continue; if (i++ > 0) - appendStringInfoString(buf, ", "); + { + if (aggref->aggformat == FUNCFMT_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); @@ -9464,6 +9486,7 @@ get_windowfunc_expr(WindowFunc *wfunc, deparse_context *context) int nargs; List *argnames; ListCell *l; + const char *funcname; if (list_length(wfunc->args) > FUNC_MAX_ARGS) ereport(ERROR, @@ -9481,16 +9504,37 @@ 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)); + switch (wfunc->winformat) + { + case FUNCFMT_JSON_OBJECTAGG: + funcname = "JSON_OBJECTAGG"; + break; + case FUNCFMT_JSON_ARRAYAGG: + funcname = "JSON_ARRAYAGG"; + break; + default: + funcname = generate_function_name(wfunc->winfnoid, nargs, argnames, + argtypes, false, NULL, + context->special_exprkind); + break; + } + + 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 (wfunc->winformat == FUNCFMT_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); + } get_func_opts(wfunc->winformat, wfunc->winformatopts, context); diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index 044695a046..88b42b47dc 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -535,14 +535,22 @@ # 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_objectagg', aggtransfn => 'json_objectagg_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_objectagg', aggtransfn => 'jsonb_objectagg_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 b0188ff0d0..cea72d1e63 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8174,6 +8174,10 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, +{ oid => '3431', 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' }, @@ -8181,10 +8185,18 @@ proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '3431', 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 => '3432', descr => 'json object aggregate transition function', + proname => 'json_objectagg_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal any any bool bool', + prosrc => 'json_objectagg_transfn' }, { oid => '3196', descr => 'json object aggregate final function', proname => 'json_object_agg_finalfn', proisstrict => 'f', prorettype => 'json', proargtypes => 'internal', @@ -8193,6 +8205,10 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '3430', descr => 'aggregate input into a json object', + proname => 'json_objectagg', prokind => 'a', proisstrict => 'f', + provolatile => 's', prorettype => 'json', proargtypes => 'any any bool bool', + 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', @@ -9037,6 +9053,10 @@ proname => 'jsonb_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'jsonb_agg_transfn' }, +{ oid => '6065', 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', @@ -9045,10 +9065,18 @@ proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +{ oid => '6063', 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 => '3433', descr => 'jsonb object aggregate transition function', + proname => 'jsonb_objectagg_transfn', proisstrict => 'f', provolatile => 's', + prorettype => 'internal', proargtypes => 'internal any any bool bool', + prosrc => 'jsonb_objectagg_transfn' }, { oid => '3269', descr => 'jsonb object aggregate final function', proname => 'jsonb_object_agg_finalfn', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'internal', @@ -9057,6 +9085,10 @@ proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +{ oid => '6064', descr => 'aggregate inputs into jsonb object', + proname => 'jsonb_objectagg', prokind => 'a', proisstrict => 'f', + prorettype => 'jsonb', proargtypes => 'any any bool bool', + 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/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 85af36ee5b..63aa3cc6df 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1405,8 +1405,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 index b3eef1e310..5fab5c5734 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -419,3 +419,160 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT [[{ "a" : 123 }]] (1 row) +-- 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 key "1" +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 key "1" diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 1227ef79f0..99cfc7baf3 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -874,8 +874,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 index 7d826660f3..b1892573c2 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -115,3 +115,90 @@ 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); + +-- 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); From 6ab06ce9af05d46c1512d2ba48eb12813b80d2f4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 23:32:41 +0300 Subject: [PATCH 26/66] Add JSON_ARRAY(subquery) transformation --- src/backend/nodes/copyfuncs.c | 20 ++++++++ src/backend/nodes/nodeFuncs.c | 10 ++++ src/backend/parser/parse_expr.c | 71 +++++++++++++++++++++++++++ src/backend/parser/parse_target.c | 1 + src/test/regress/expected/sqljson.out | 40 +++++++++++++++ src/test/regress/sql/sqljson.sql | 11 +++++ 6 files changed, 153 insertions(+) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a63325dddd..a82d736870 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2340,6 +2340,23 @@ _copyJsonArrayAgg(const JsonArrayAgg *from) return newnode; } +/* + * _copyJsonArrayQueryCtor + */ +static JsonArrayQueryCtor * +_copyJsonArrayQueryCtor(const JsonArrayQueryCtor *from) +{ + JsonArrayQueryCtor *newnode = makeNode(JsonArrayQueryCtor); + + COPY_NODE_FIELD(query); + COPY_NODE_FIELD(output); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(absent_on_null); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5262,6 +5279,9 @@ copyObjectImpl(const void *from) case T_JsonArrayCtor: retval = _copyJsonArrayCtor(from); break; + case T_JsonArrayQueryCtor: + retval = _copyJsonArrayQueryCtor(from); + break; case T_JsonArrayAgg: retval = _copyJsonArrayAgg(from); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 298852eb90..0f7602078e 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3837,6 +3837,16 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonArrayQueryCtor: + { + JsonArrayQueryCtor *jaqc = (JsonArrayQueryCtor *) node; + + if (walker(jaqc->output, context)) + return true; + if (walker(jaqc->query, context)) + return true; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 1c33fc74eb..263b647432 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -125,6 +125,8 @@ static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); static Node *transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor); static Node *transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor); +static Node *transformJsonArrayQueryCtor(ParseState *pstate, + JsonArrayQueryCtor *ctor); static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); static Node *make_row_comparison_op(ParseState *pstate, List *opname, @@ -383,6 +385,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonArrayCtor(pstate, (JsonArrayCtor *) expr); break; + case T_JsonArrayQueryCtor: + result = transformJsonArrayQueryCtor(pstate, (JsonArrayQueryCtor *) expr); + break; + case T_JsonObjectAgg: result = transformJsonObjectAgg(pstate, (JsonObjectAgg *) expr); break; @@ -3970,6 +3976,71 @@ transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); } +/* + * Transform JSON_ARRAY(query [FORMAT] [RETURNING] [ON NULL]) into + * (SELECT JSON_ARRAYAGG(a [FORMAT] [RETURNING] [ON NULL]) FROM (query) q(a)) + */ +static Node * +transformJsonArrayQueryCtor(ParseState *pstate, JsonArrayQueryCtor *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->ctor.agg_order = NIL; + agg->ctor.output = ctor->output; + agg->absent_on_null = ctor->absent_on_null; + agg->ctor.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. */ diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 4a89d816a7..a06c5cb668 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1930,6 +1930,7 @@ FigureColnameInternal(Node *node, char **name) *name = "json_object"; return 2; case T_JsonArrayCtor: + case T_JsonArrayQueryCtor: *name = "json_array"; return 2; case T_JsonObjectAgg: diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 5fab5c5734..5256bbd9c5 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -419,6 +419,46 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT [[{ "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 diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index b1892573c2..c1ea28b98d 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -116,6 +116,17 @@ 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 fbbaaf949b959653f614174c4eccaee8cf114a96 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 03:47:28 +0300 Subject: [PATCH 27/66] Add tests for deparsing of SQL/JSON constructors --- src/test/regress/expected/sqljson.out | 124 ++++++++++++++++++++++++++ src/test/regress/sql/sqljson.sql | 67 ++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 5256bbd9c5..7b2e2f7c33 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -616,3 +616,127 @@ ERROR: duplicate JSON key "1" 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 key "1" +-- 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 FORMAT 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 FORMAT 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; diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index c1ea28b98d..aaef2d8aab 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -213,3 +213,70 @@ 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; From ef154797b1202db1b9064aef3b1881a1cd299cc8 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Feb 2017 18:45:43 +0300 Subject: [PATCH 28/66] Add IS JSON predicate transformation --- .../pg_stat_statements/pg_stat_statements.c | 8 + src/backend/nodes/copyfuncs.c | 36 ++++ src/backend/nodes/equalfuncs.c | 13 ++ src/backend/nodes/nodeFuncs.c | 2 + src/backend/nodes/outfuncs.c | 12 ++ src/backend/nodes/readfuncs.c | 16 ++ src/backend/parser/parse_expr.c | 110 ++++++++++ src/backend/utils/adt/json.c | 187 +++++++++++++++++ src/backend/utils/adt/jsonb.c | 35 ++++ src/backend/utils/adt/ruleutils.c | 50 ++++- src/include/catalog/pg_proc.dat | 10 + src/include/nodes/nodes.h | 1 + src/include/nodes/parsenodes.h | 7 + src/include/nodes/primnodes.h | 3 +- src/test/regress/expected/opr_sanity.out | 3 +- src/test/regress/expected/sqljson.out | 198 ++++++++++++++++++ src/test/regress/sql/sqljson.sql | 96 +++++++++ 17 files changed, 783 insertions(+), 4 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 9ebde14ebd..3b220574d5 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2911,6 +2911,14 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) APP_JUMB(opts->absent_on_null); } break; + case T_JsonIsPredicateOpts: + { + JsonIsPredicateOpts *opts = (JsonIsPredicateOpts *) node; + + APP_JUMB(opts->unique_keys); + APP_JUMB(opts->value_type); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index a82d736870..43117e89da 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2357,6 +2357,36 @@ _copyJsonArrayQueryCtor(const JsonArrayQueryCtor *from) return newnode; } +/* + * _copyJsonIsPredicate + */ +static JsonIsPredicate * +_copyJsonIsPredicate(const JsonIsPredicate *from) +{ + JsonIsPredicate *newnode = makeNode(JsonIsPredicate); + + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(vtype); + COPY_SCALAR_FIELD(unique_keys); + + return newnode; +} + +/* + * _copyJsonIsPredicateOpts + */ +static JsonIsPredicateOpts * +_copyJsonIsPredicateOpts(const JsonIsPredicateOpts *from) +{ + JsonIsPredicateOpts *newnode = makeNode(JsonIsPredicateOpts); + + COPY_SCALAR_FIELD(value_type); + COPY_SCALAR_FIELD(unique_keys); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5285,6 +5315,12 @@ copyObjectImpl(const void *from) case T_JsonArrayAgg: retval = _copyJsonArrayAgg(from); break; + case T_JsonIsPredicate: + retval = _copyJsonIsPredicate(from); + break; + case T_JsonIsPredicateOpts: + retval = _copyJsonIsPredicateOpts(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 70f123433f..d864bea650 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -845,6 +845,16 @@ _equalJsonCtorOpts(const JsonCtorOpts *a, const JsonCtorOpts *b) return true; } +static bool +_equalJsonIsPredicateOpts(const JsonIsPredicateOpts *a, + const JsonIsPredicateOpts *b) +{ + COMPARE_SCALAR_FIELD(value_type); + COMPARE_SCALAR_FIELD(unique_keys); + + return true; +} + /* * Stuff from pathnodes.h */ @@ -3212,6 +3222,9 @@ equal(const void *a, const void *b) case T_JsonCtorOpts: retval = _equalJsonCtorOpts(a, b); break; + case T_JsonIsPredicateOpts: + retval = _equalJsonIsPredicateOpts(a, b); + break; /* * RELATION NODES diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 0f7602078e..39ff235b77 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3847,6 +3847,8 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonIsPredicate: + return walker(((JsonIsPredicate *) node)->expr, context); 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 13e8404662..02f287f998 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1711,6 +1711,15 @@ _outJsonCtorOpts(StringInfo str, const JsonCtorOpts *node) WRITE_BOOL_FIELD(absent_on_null); } +static void +_outJsonIsPredicateOpts(StringInfo str, const JsonIsPredicateOpts *node) +{ + WRITE_NODE_TYPE("JSONISOPTS"); + + WRITE_ENUM_FIELD(value_type, JsonValueType); + WRITE_BOOL_FIELD(unique_keys); +} + /***************************************************************************** * * Stuff from pathnodes.h. @@ -4311,6 +4320,9 @@ outNode(StringInfo str, const void *obj) case T_JsonCtorOpts: _outJsonCtorOpts(str, obj); break; + case T_JsonIsPredicateOpts: + _outJsonIsPredicateOpts(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 3acc4c85b4..5a2772586d 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1383,6 +1383,20 @@ _readJsonCtorOpts(void) READ_DONE(); } +/* + * _readJsonIsPredicateOpts + */ +static JsonIsPredicateOpts * +_readJsonIsPredicateOpts() +{ + READ_LOCALS(JsonIsPredicateOpts); + + READ_ENUM_FIELD(value_type, JsonValueType); + READ_BOOL_FIELD(unique_keys); + + READ_DONE(); +} + /* * Stuff from parsenodes.h. */ @@ -2850,6 +2864,8 @@ parseNodeString(void) return_value = _readJsonValueExpr(); else if (MATCH("JSONCTOROPTS", 12)) return_value = _readJsonCtorOpts(); + else if (MATCH("JSONISOPTS", 10)) + return_value = _readJsonIsPredicateOpts(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 263b647432..a65d1f0bdf 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -129,6 +129,7 @@ static Node *transformJsonArrayQueryCtor(ParseState *pstate, JsonArrayQueryCtor *ctor); static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); +static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p); 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, @@ -397,6 +398,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); break; + case T_JsonIsPredicate: + result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -4262,3 +4267,108 @@ transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor) return coerceJsonFuncExpr(pstate, (Node *) fexpr, &returning, true); } + +static const char * +JsonValueTypeStrings[] = +{ + "any", + "object", + "array", + "scalar", +}; + +static Const * +makeJsonValueTypeConst(JsonValueType type) +{ + return makeConst(TEXTOID, -1, InvalidOid, -1, + PointerGetDatum(cstring_to_text( + JsonValueTypeStrings[(int) type])), + false, false); +} + +/* + * Transform IS JSON predicate into + * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call. + */ +static Node * +transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) +{ + Node *expr = transformExprRecurse(pstate, pred->expr); + Oid exprtype = exprType(expr); + FuncExpr *fexpr; + JsonIsPredicateOpts *opts; + + /* prepare input document */ + if (exprtype == BYTEAOID) + { + expr = makeJsonByteaToTextConversion(expr, &pred->format, + exprLocation(expr)); + exprtype = TEXTOID; + } + 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 (pred->format.encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, pred->format.location), + errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types"))); + } + + expr = (Node *) makeJsonValueExpr((Expr *) expr, pred->format); + + /* make resulting expression */ + if (exprtype == TEXTOID || exprtype == JSONOID) + { + fexpr = makeFuncExpr(F_JSON_IS_VALID, BOOLOID, + list_make3(expr, + makeJsonValueTypeConst(pred->vtype), + makeBoolConst(pred->unique_keys, false)), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + fexpr->location = pred->location; + } + else if (exprtype == JSONBOID) + { + /* XXX the following expressions also can be used here: + * jsonb_type(jsonb) = 'type' (for object and array checks) + * CASE jsonb_type(jsonb) WHEN ... END (for scalars checks) + */ + fexpr = makeFuncExpr(F_JSONB_IS_VALID, BOOLOID, + list_make2(expr, + makeJsonValueTypeConst(pred->vtype)), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + + fexpr->location = pred->location; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use type %s in IS JSON predicate", + format_type_be(exprtype)))); + return NULL; + } + + opts = makeNode(JsonIsPredicateOpts); + opts->unique_keys = pred->unique_keys; + opts->value_type = pred->vtype; + + fexpr->funcformat2 = FUNCFMT_IS_JSON; + fexpr->funcformatopts = (Node *) opts; + + return (Node *) fexpr; +} diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 4f7aabc648..3da6e725a5 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "access/hash.h" #include "access/htup_details.h" #include "access/transam.h" #include "catalog/pg_type.h" @@ -93,6 +94,20 @@ typedef struct JsonAggState JsonUniqueCheckContext unique_check; } JsonAggState; +/* Element of object stack for key uniqueness check */ +typedef struct JsonObjectFields +{ + struct JsonObjectFields *parent; + HTAB *fields; +} JsonObjectFields; + +/* State for key uniqueness check */ +typedef struct JsonUniqueState +{ + JsonLexContext *lex; + JsonObjectFields *stack; +} JsonUniqueState; + static inline void json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, @@ -2801,6 +2816,178 @@ escape_json(StringInfo buf, const char *str) appendStringInfoCharMacro(buf, '"'); } +/* Functions implementing hash table for key uniqueness check */ +static int +json_unique_hash_match(const void *key1, const void *key2, Size keysize) +{ + return strcmp(*(const char **) key1, *(const char **) key2); +} + +static void * +json_unique_hash_keycopy(void *dest, const void *src, Size keysize) +{ + *(const char **) dest = pstrdup(*(const char **) src); + + return dest; +} + +static uint32 +json_unique_hash(const void *key, Size keysize) +{ + const char *s = *(const char **) key; + + return DatumGetUInt32(hash_any((const unsigned char *) s, (int) strlen(s))); +} + +/* Semantic actions for key uniqueness check */ +static void +json_unique_object_start(void *_state) +{ + JsonUniqueState *state = _state; + JsonObjectFields *obj = palloc(sizeof(*obj)); + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(char *); + ctl.entrysize = sizeof(char *); + ctl.hcxt = CurrentMemoryContext; + ctl.hash = json_unique_hash; + ctl.keycopy = json_unique_hash_keycopy; + ctl.match = json_unique_hash_match; + obj->fields = hash_create("json object hashtable", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT | + HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY); + obj->parent = state->stack; /* push object to stack */ + + state->stack = obj; +} + +static void +json_unique_object_end(void *_state) +{ + JsonUniqueState *state = _state; + + hash_destroy(state->stack->fields); + + state->stack = state->stack->parent; /* pop object from stack */ +} + +static void +json_unique_object_field_start(void *_state, char *field, bool isnull) +{ + JsonUniqueState *state = _state; + bool found; + + /* find key collision in the current object */ + (void) hash_search(state->stack->fields, &field, HASH_ENTER, &found); + + if (found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("duplicate JSON key \"%s\"", field), + report_json_context(state->lex))); +} + +/* + * json_is_valid -- check json text validity, its value type and key uniqueness + */ +Datum +json_is_valid(PG_FUNCTION_ARGS) +{ + text *json = PG_GETARG_TEXT_P(0); + text *type = PG_GETARG_TEXT_P(1); + bool unique = PG_GETARG_BOOL(2); + MemoryContext mcxt = CurrentMemoryContext; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(1) && + strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + JsonLexContext *lex; + JsonTokenType tok; + + lex = makeJsonLexContext(json, false); + + /* Lex exactly one token from the input and check its type. */ + PG_TRY(); + { + json_lex(lex); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) + { + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + PG_RETURN_BOOL(false); /* invalid json */ + } + PG_RE_THROW(); + } + PG_END_TRY(); + + tok = lex_peek(lex); + + if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (tok != JSON_TOKEN_OBJECT_START) + PG_RETURN_BOOL(false); /* json is not a object */ + } + else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (tok != JSON_TOKEN_ARRAY_START) + PG_RETURN_BOOL(false); /* json is not an array */ + } + else + { + if (tok == JSON_TOKEN_OBJECT_START || + tok == JSON_TOKEN_ARRAY_START) + PG_RETURN_BOOL(false); /* json is not a scalar */ + } + } + + /* do full parsing pass only for uniqueness check or JSON text validation */ + if (unique || + get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONOID) + { + JsonLexContext *lex = makeJsonLexContext(json, unique); + JsonSemAction uniqueSemAction = {0}; + JsonUniqueState state; + + if (unique) + { + state.lex = lex; + state.stack = NULL; + + 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; + } + + PG_TRY(); + { + pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) + { + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + PG_RETURN_BOOL(false); /* invalid json or key collision found */ + } + PG_RE_THROW(); + } + PG_END_TRY(); + } + + PG_RETURN_BOOL(true); /* ok */ +} + /* * SQL function json_typeof(json) -> text * diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index dbb056ee1b..d6917b4161 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2120,6 +2120,41 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) } +/* + * jsonb_is_valid -- check jsonb value type + */ +Datum +jsonb_is_valid(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + text *type = PG_GETARG_TEXT_P(1); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (!PG_ARGISNULL(1) && + strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!strncmp("object", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!JB_ROOT_IS_OBJECT(jb)) + PG_RETURN_BOOL(false); + } + else if (!strncmp("array", VARDATA(type), VARSIZE_ANY_EXHDR(type))) + { + if (!JB_ROOT_IS_ARRAY(jb) || JB_ROOT_IS_SCALAR(jb)) + PG_RETURN_BOOL(false); + } + else + { + if (!JB_ROOT_IS_ARRAY(jb) || !JB_ROOT_IS_SCALAR(jb)) + PG_RETURN_BOOL(false); + } + } + + PG_RETURN_BOOL(true); +} + /* * Extract scalar value from raw-scalar pseudo-array jsonb. */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 8a923c481e..00a0bb9201 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9204,6 +9204,37 @@ get_func_opts(FuncFormat aggformat, Node *aggformatopts, deparse_context *contex get_json_returning(&opts->returning, context, true); } break; + + case FUNCFMT_IS_JSON: + { + JsonIsPredicateOpts *opts = + castNode(JsonIsPredicateOpts, aggformatopts); + + appendStringInfoString(context->buf, " IS JSON"); + + if (!opts) + break; + + switch (opts->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 (opts->unique_keys) + appendStringInfoString(context->buf, " WITH UNIQUE KEYS"); + } + break; + default: break; } @@ -9221,6 +9252,7 @@ get_func_expr(FuncExpr *expr, deparse_context *context, Oid argtypes[FUNC_MAX_ARGS]; int nargs; int firstarg; + int lastarg = list_length(expr->args); List *argnames; bool use_variadic; ListCell *l; @@ -9293,6 +9325,13 @@ get_func_expr(FuncExpr *expr, deparse_context *context, use_variadic = false; break; + case FUNCFMT_IS_JSON: + funcname = NULL; + firstarg = 0; + lastarg = 0; + use_variadic = false; + break; + default: funcname = generate_function_name(funcoid, nargs, argnames, argtypes, @@ -9303,11 +9342,17 @@ get_func_expr(FuncExpr *expr, deparse_context *context, break; } - appendStringInfo(buf, "%s(", funcname); + if (funcname) + appendStringInfo(buf, "%s(", funcname); + else if (!PRETTY_PAREN(context)) + appendStringInfoChar(buf, '('); nargs = 0; foreach(l, expr->args) { + if (nargs > lastarg) + break; + if (nargs++ < firstarg) continue; @@ -9326,7 +9371,8 @@ get_func_expr(FuncExpr *expr, deparse_context *context, get_func_opts(expr->funcformat2, expr->funcformatopts, context); - appendStringInfoChar(buf, ')'); + if (funcname || !PRETTY_PAREN(context)) + appendStringInfoChar(buf, ')'); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index cea72d1e63..ae869b0c8e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8250,6 +8250,13 @@ { oid => '3261', descr => 'remove object fields with null values from json', proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json', prosrc => 'json_strip_nulls' }, +{ oid => '6073', descr => 'check json value type and key uniqueness', + proname => 'json_is_valid', prorettype => 'bool', + proargtypes => 'json text bool', prosrc => 'json_is_valid' }, +{ oid => '6074', + descr => 'check json text validity, value type and key uniquenes', + proname => 'json_is_valid', prorettype => 'bool', + proargtypes => 'text text bool', prosrc => 'json_is_valid' }, { oid => '3947', proname => 'json_object_field', prorettype => 'json', @@ -9121,6 +9128,9 @@ { oid => '3262', descr => 'remove object fields with null values from jsonb', proname => 'jsonb_strip_nulls', prorettype => 'jsonb', proargtypes => 'jsonb', prosrc => 'jsonb_strip_nulls' }, +{ oid => '6062', descr => 'check jsonb value type', + proname => 'jsonb_is_valid', prorettype => 'bool', + proargtypes => 'jsonb text', prosrc => 'jsonb_is_valid' }, { oid => '3478', proname => 'jsonb_object_field', prorettype => 'jsonb', diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d8e85ef412..35918e501a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -491,6 +491,7 @@ typedef enum NodeTag T_JsonBehavior, T_JsonOutput, T_JsonCtorOpts, + T_JsonIsPredicateOpts, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 24dd036f8d..342ce6f1f1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1572,6 +1572,13 @@ typedef struct JsonIsPredicate int location; /* token location, or -1 if unknown */ } JsonIsPredicate; +typedef struct JsonIsPredicateOpts +{ + NodeTag type; + JsonValueType value_type; /* JSON item type */ + bool unique_keys; /* check key uniqueness? */ +} JsonIsPredicateOpts; + /* * JsonKeyValue - * untransformed representation of JSON object key-value pair for diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6b57b7893f..bccdfef7d6 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -259,7 +259,8 @@ typedef enum FuncFormat FUNCFMT_JSON_OBJECT = 1, FUNCFMT_JSON_ARRAY = 2, FUNCFMT_JSON_OBJECTAGG = 3, - FUNCFMT_JSON_ARRAYAGG = 4 + FUNCFMT_JSON_ARRAYAGG = 4, + FUNCFMT_IS_JSON = 5 } FuncFormat; /* diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 63aa3cc6df..21de75e656 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -210,11 +210,12 @@ WHERE p1.oid != p2.oid AND ORDER BY 1, 2; proargtypes | proargtypes -------------+------------- + 25 | 114 25 | 1042 25 | 1043 1114 | 1184 1560 | 1562 -(4 rows) +(5 rows) SELECT DISTINCT p1.proargtypes[1], p2.proargtypes[1] FROM pg_proc AS p1, pg_proc AS p2 diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 7b2e2f7c33..be2add55a5 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -740,3 +740,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS 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/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index aaef2d8aab..4f3c06dcb3 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -280,3 +280,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING \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; From 586a14d9fc6cb5f632f19e377933773eb0f6d022 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sun, 17 Feb 2019 03:12:54 +0300 Subject: [PATCH 29/66] Remove PG_TRY/PG_CATCH in json_is_valid() --- src/backend/utils/adt/json.c | 223 ++++++++++++++++++++++++----------- src/include/utils/jsonapi.h | 4 +- 2 files changed, 157 insertions(+), 70 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 3da6e725a5..0e7e18a4bf 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -108,14 +108,14 @@ typedef struct JsonUniqueState JsonObjectFields *stack; } JsonUniqueState; -static inline void json_lex(JsonLexContext *lex); +static inline bool json_lex(JsonLexContext *lex); static inline void json_lex_string(JsonLexContext *lex); static inline void json_lex_number(JsonLexContext *lex, char *s, bool *num_err, int *total_len); static inline void parse_scalar(JsonLexContext *lex, JsonSemAction *sem); -static void parse_object_field(JsonLexContext *lex, JsonSemAction *sem); +static bool parse_object_field(JsonLexContext *lex, JsonSemAction *sem); static void parse_object(JsonLexContext *lex, JsonSemAction *sem); -static void parse_array_element(JsonLexContext *lex, JsonSemAction *sem); +static bool parse_array_element(JsonLexContext *lex, JsonSemAction *sem); static void parse_array(JsonLexContext *lex, JsonSemAction *sem); static void report_parse_error(JsonParseContext ctx, JsonLexContext *lex) pg_attribute_noreturn(); static void report_invalid_token(JsonLexContext *lex) pg_attribute_noreturn(); @@ -183,6 +183,16 @@ lex_peek_value(JsonLexContext *lex) } } +static inline bool +lex_set_error(JsonLexContext *lex) +{ + if (lex->throw_errors) + return false; + + lex->error = true; + return true; +} + /* * lex_accept * @@ -200,8 +210,7 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme) if (lexeme != NULL) *lexeme = lex_peek_value(lex); - json_lex(lex); - return true; + return json_lex(lex); } return false; } @@ -212,11 +221,16 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme) * move the lexer to the next token if the current look_ahead token matches * the parameter token. Otherwise, report an error. */ -static inline void +static inline bool lex_expect(JsonParseContext ctx, JsonLexContext *lex, JsonTokenType token) { - if (!lex_accept(lex, token, NULL)) + if (lex_accept(lex, token, NULL)) + return true; + + if (!lex_set_error(lex)) report_parse_error(ctx, lex); + + return false; } /* chars to consider as part of an alphanumeric token */ @@ -276,7 +290,7 @@ json_in(PG_FUNCTION_ARGS) /* validate it */ lex = makeJsonLexContext(result, false); - pg_parse_json(lex, &nullSemAction); + (void) pg_parse_json(lex, &nullSemAction); /* Internal representation is the same as text, for now */ PG_RETURN_TEXT_P(result); @@ -323,7 +337,7 @@ json_recv(PG_FUNCTION_ARGS) /* Validate it. */ lex = makeJsonLexContextCstringLen(str, nbytes, false); - pg_parse_json(lex, &nullSemAction); + (void) pg_parse_json(lex, &nullSemAction); PG_RETURN_TEXT_P(cstring_to_text_with_len(str, nbytes)); } @@ -356,6 +370,7 @@ makeJsonLexContextCstringLen(char *json, int len, bool need_escapes) lex->input = lex->token_terminator = lex->line_start = json; lex->line_number = 1; lex->input_length = len; + lex->throw_errors = true; if (need_escapes) lex->strval = makeStringInfo(); return lex; @@ -371,13 +386,14 @@ makeJsonLexContextCstringLen(char *json, int len, bool need_escapes) * action routines to be called at appropriate spots during parsing, and a * pointer to a state object to be passed to those routines. */ -void +bool pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) { JsonTokenType tok; /* get the initial token */ - json_lex(lex); + if (!json_lex(lex)) + return false; tok = lex_peek(lex); @@ -394,8 +410,7 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) parse_scalar(lex, sem); /* json can be a bare scalar */ } - lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END); - + return !lex->error && lex_expect(JSON_PARSE_END, lex, JSON_TOKEN_END); } /* @@ -420,6 +435,7 @@ json_count_array_elements(JsonLexContext *lex) memcpy(©lex, lex, sizeof(JsonLexContext)); copylex.strval = NULL; /* not interested in values here */ copylex.lex_level++; + copylex.throw_errors = true; count = 0; lex_expect(JSON_PARSE_ARRAY_START, ©lex, JSON_TOKEN_ARRAY_START); @@ -475,14 +491,16 @@ parse_scalar(JsonLexContext *lex, JsonSemAction *sem) lex_accept(lex, JSON_TOKEN_STRING, valaddr); break; default: - report_parse_error(JSON_PARSE_VALUE, lex); + if (!lex_set_error(lex)) + report_parse_error(JSON_PARSE_VALUE, lex); + return; } - if (sfunc != NULL) + if (sfunc != NULL && !lex->error) (*sfunc) (sem->semstate, val, tok); } -static void +static bool parse_object_field(JsonLexContext *lex, JsonSemAction *sem) { /* @@ -502,16 +520,26 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) fnameaddr = &fname; if (!lex_accept(lex, JSON_TOKEN_STRING, fnameaddr)) - report_parse_error(JSON_PARSE_STRING, lex); + { + if (!lex_set_error(lex)) + report_parse_error(JSON_PARSE_STRING, lex); + return false; + } - lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON); + if (!lex_expect(JSON_PARSE_OBJECT_LABEL, lex, JSON_TOKEN_COLON)) + return false; tok = lex_peek(lex); isnull = tok == JSON_TOKEN_NULL; if (ostart != NULL) + { (*ostart) (sem->semstate, fname, isnull); + if (lex->error) + return false; + } + switch (tok) { case JSON_TOKEN_OBJECT_START: @@ -524,8 +552,13 @@ parse_object_field(JsonLexContext *lex, JsonSemAction *sem) parse_scalar(lex, sem); } + if (lex->error) + return false; + if (oend != NULL) (*oend) (sem->semstate, fname, isnull); + + return !lex->error; } static void @@ -542,8 +575,13 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) check_stack_depth(); if (ostart != NULL) + { (*ostart) (sem->semstate); + if (lex->error) + return; + } + /* * Data inside an object is at a higher nesting level than the object * itself. Note that we increment this after we call the semantic routine @@ -553,24 +591,30 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) lex->lex_level++; /* we know this will succeed, just clearing the token */ - lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START); + if (!lex_expect(JSON_PARSE_OBJECT_START, lex, JSON_TOKEN_OBJECT_START)) + return; tok = lex_peek(lex); switch (tok) { case JSON_TOKEN_STRING: - parse_object_field(lex, sem); + if (!parse_object_field(lex, sem)) + return; while (lex_accept(lex, JSON_TOKEN_COMMA, NULL)) - parse_object_field(lex, sem); + if (!parse_object_field(lex, sem)) + return; break; case JSON_TOKEN_OBJECT_END: break; default: /* case of an invalid initial token inside the object */ - report_parse_error(JSON_PARSE_OBJECT_START, lex); + if (!lex_set_error(lex)) + report_parse_error(JSON_PARSE_OBJECT_START, lex); + return; } - lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END); + if (!lex_expect(JSON_PARSE_OBJECT_NEXT, lex, JSON_TOKEN_OBJECT_END)) + return; lex->lex_level--; @@ -578,7 +622,7 @@ parse_object(JsonLexContext *lex, JsonSemAction *sem) (*oend) (sem->semstate); } -static void +static bool parse_array_element(JsonLexContext *lex, JsonSemAction *sem) { json_aelem_action astart = sem->array_element_start; @@ -590,8 +634,13 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem) isnull = tok == JSON_TOKEN_NULL; if (astart != NULL) + { (*astart) (sem->semstate, isnull); + if (lex->error) + return false; + } + /* an array element is any object, array or scalar */ switch (tok) { @@ -605,8 +654,13 @@ parse_array_element(JsonLexContext *lex, JsonSemAction *sem) parse_scalar(lex, sem); } + if (lex->error) + return false; + if (aend != NULL) (*aend) (sem->semstate, isnull); + + return !lex->error; } static void @@ -622,8 +676,13 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) check_stack_depth(); if (astart != NULL) + { (*astart) (sem->semstate); + if (lex->error) + return; + } + /* * Data inside an array is at a higher nesting level than the array * itself. Note that we increment this after we call the semantic routine @@ -632,17 +691,21 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) */ lex->lex_level++; - lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START); + if (!lex_expect(JSON_PARSE_ARRAY_START, lex, JSON_TOKEN_ARRAY_START)) + return; + if (lex_peek(lex) != JSON_TOKEN_ARRAY_END) { - - parse_array_element(lex, sem); + if (!parse_array_element(lex, sem)) + return; while (lex_accept(lex, JSON_TOKEN_COMMA, NULL)) - parse_array_element(lex, sem); + if (!parse_array_element(lex, sem)) + return; } - lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END); + if (!lex_expect(JSON_PARSE_ARRAY_NEXT, lex, JSON_TOKEN_ARRAY_END)) + return; lex->lex_level--; @@ -653,7 +716,7 @@ parse_array(JsonLexContext *lex, JsonSemAction *sem) /* * Lex one token from the input stream. */ -static inline void +static inline bool json_lex(JsonLexContext *lex) { char *s; @@ -763,7 +826,9 @@ json_lex(JsonLexContext *lex) { lex->prev_token_terminator = lex->token_terminator; lex->token_terminator = s + 1; - report_invalid_token(lex); + if (!lex_set_error(lex)) + report_invalid_token(lex); + return false; } /* @@ -779,16 +844,18 @@ json_lex(JsonLexContext *lex) lex->token_type = JSON_TOKEN_TRUE; else if (memcmp(s, "null", 4) == 0) lex->token_type = JSON_TOKEN_NULL; - else + else if (!lex_set_error(lex)) report_invalid_token(lex); } else if (p - s == 5 && memcmp(s, "false", 5) == 0) lex->token_type = JSON_TOKEN_FALSE; - else + else if (!lex_set_error(lex)) report_invalid_token(lex); } } /* end of switch */ + + return !lex->error; } /* @@ -815,7 +882,9 @@ json_lex_string(JsonLexContext *lex) if (len >= lex->input_length) { lex->token_terminator = s; - report_invalid_token(lex); + if (!lex_set_error(lex)) + report_invalid_token(lex); + return; } else if (*s == '"') break; @@ -824,6 +893,8 @@ json_lex_string(JsonLexContext *lex) /* Per RFC4627, these characters MUST be escaped. */ /* Since *s isn't printable, exclude it from the context string */ lex->token_terminator = s; + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), @@ -839,7 +910,9 @@ json_lex_string(JsonLexContext *lex) if (len >= lex->input_length) { lex->token_terminator = s; - report_invalid_token(lex); + if (!lex_set_error(lex)) + report_invalid_token(lex); + return; } else if (*s == 'u') { @@ -853,7 +926,9 @@ json_lex_string(JsonLexContext *lex) if (len >= lex->input_length) { lex->token_terminator = s; - report_invalid_token(lex); + if (!lex_set_error(lex)) + report_invalid_token(lex); + return; } else if (*s >= '0' && *s <= '9') ch = (ch * 16) + (*s - '0'); @@ -863,6 +938,8 @@ json_lex_string(JsonLexContext *lex) ch = (ch * 16) + (*s - 'A') + 10; else { + if (lex_set_error(lex)) + return; lex->token_terminator = s + pg_mblen(s); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), @@ -880,33 +957,45 @@ json_lex_string(JsonLexContext *lex) if (ch >= 0xd800 && ch <= 0xdbff) { if (hi_surrogate != -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode high surrogate must not follow a high surrogate."), report_json_context(lex))); + } hi_surrogate = (ch & 0x3ff) << 10; continue; } else if (ch >= 0xdc00 && ch <= 0xdfff) { if (hi_surrogate == -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode low surrogate must follow a high surrogate."), report_json_context(lex))); + } ch = 0x10000 + hi_surrogate + (ch & 0x3ff); hi_surrogate = -1; } if (hi_surrogate != -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode low surrogate must follow a high surrogate."), report_json_context(lex))); + } /* * For UTF8, replace the escape sequence by the actual @@ -918,6 +1007,8 @@ json_lex_string(JsonLexContext *lex) if (ch == 0) { /* We can't allow this, since our TEXT type doesn't */ + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("unsupported Unicode escape sequence"), @@ -941,6 +1032,8 @@ json_lex_string(JsonLexContext *lex) } else { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), errmsg("unsupported Unicode escape sequence"), @@ -953,12 +1046,16 @@ json_lex_string(JsonLexContext *lex) else if (lex->strval != NULL) { if (hi_surrogate != -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode low surrogate must follow a high surrogate."), report_json_context(lex))); + } switch (*s) { @@ -984,6 +1081,8 @@ json_lex_string(JsonLexContext *lex) break; default: /* Not a valid string escape, so error out. */ + if (lex_set_error(lex)) + return; lex->token_terminator = s + pg_mblen(s); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), @@ -1003,6 +1102,8 @@ json_lex_string(JsonLexContext *lex) * replace it with a switch statement, but testing so far has * shown it's not a performance win. */ + if (lex_set_error(lex)) + return; lex->token_terminator = s + pg_mblen(s); ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), @@ -1016,11 +1117,15 @@ json_lex_string(JsonLexContext *lex) else if (lex->strval != NULL) { if (hi_surrogate != -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode low surrogate must follow a high surrogate."), report_json_context(lex))); + } appendStringInfoChar(lex->strval, *s); } @@ -1028,11 +1133,15 @@ json_lex_string(JsonLexContext *lex) } if (hi_surrogate != -1) + { + if (lex_set_error(lex)) + return; ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for type %s", "json"), errdetail("Unicode low surrogate must follow a high surrogate."), report_json_context(lex))); + } /* Hooray, we found the end of the string! */ lex->prev_token_terminator = lex->token_terminator; @@ -1155,7 +1264,7 @@ json_lex_number(JsonLexContext *lex, char *s, lex->prev_token_terminator = lex->token_terminator; lex->token_terminator = s; /* handle error if any */ - if (error) + if (error && !lex_set_error(lex)) report_invalid_token(lex); } } @@ -2883,7 +2992,7 @@ json_unique_object_field_start(void *_state, char *field, bool isnull) /* find key collision in the current object */ (void) hash_search(state->stack->fields, &field, HASH_ENTER, &found); - if (found) + if (found && !lex_set_error(state->lex)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("duplicate JSON key \"%s\"", field), @@ -2899,7 +3008,6 @@ json_is_valid(PG_FUNCTION_ARGS) text *json = PG_GETARG_TEXT_P(0); text *type = PG_GETARG_TEXT_P(1); bool unique = PG_GETARG_BOOL(2); - MemoryContext mcxt = CurrentMemoryContext; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); @@ -2911,23 +3019,11 @@ json_is_valid(PG_FUNCTION_ARGS) JsonTokenType tok; lex = makeJsonLexContext(json, false); + lex->throw_errors = false; /* Lex exactly one token from the input and check its type. */ - PG_TRY(); - { - json_lex(lex); - } - PG_CATCH(); - { - if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) - { - FlushErrorState(); - MemoryContextSwitchTo(mcxt); - PG_RETURN_BOOL(false); /* invalid json */ - } - PG_RE_THROW(); - } - PG_END_TRY(); + if (!json_lex(lex)) + PG_RETURN_BOOL(false); /* invalid json */ tok = lex_peek(lex); @@ -2968,21 +3064,10 @@ json_is_valid(PG_FUNCTION_ARGS) uniqueSemAction.object_end = json_unique_object_end; } - PG_TRY(); - { - pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction); - } - PG_CATCH(); - { - if (ERRCODE_TO_CATEGORY(geterrcode()) == ERRCODE_DATA_EXCEPTION) - { - FlushErrorState(); - MemoryContextSwitchTo(mcxt); - PG_RETURN_BOOL(false); /* invalid json or key collision found */ - } - PG_RE_THROW(); - } - PG_END_TRY(); + lex->throw_errors = false; + + if (!pg_parse_json(lex, unique ? &uniqueSemAction : &nullSemAction)) + PG_RETURN_BOOL(false); /* invalid json or key collision found */ } PG_RETURN_BOOL(true); /* ok */ diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index f6ad491b11..8d391b2965 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -61,6 +61,8 @@ typedef struct JsonLexContext int line_number; char *line_start; StringInfo strval; + bool throw_errors; + bool error; } JsonLexContext; typedef void (*json_struct_action) (void *state); @@ -153,7 +155,7 @@ typedef struct JsonIterator * points to. If the action pointers are NULL the parser * does nothing and just continues. */ -extern void pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); +extern bool pg_parse_json(JsonLexContext *lex, JsonSemAction *sem); /* * json_count_array_elements performs a fast secondary parse to determine the From eb62e38b797c37710e74b2a726de24295274c61e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Feb 2017 18:45:43 +0300 Subject: [PATCH 30/66] Add JSON_VALUE, JSON_EXISTS, JSON_QUERY --- .../pg_stat_statements/pg_stat_statements.c | 12 + src/backend/executor/execExpr.c | 87 ++ src/backend/executor/execExprInterp.c | 433 +++++++++ src/backend/jit/llvm/llvmjit_expr.c | 7 + src/backend/nodes/copyfuncs.c | 154 ++++ src/backend/nodes/equalfuncs.c | 76 ++ src/backend/nodes/nodeFuncs.c | 181 ++++ src/backend/nodes/outfuncs.c | 67 ++ src/backend/nodes/readfuncs.c | 80 +- src/backend/optimizer/path/costsize.c | 3 +- src/backend/parser/parse_collate.c | 4 + src/backend/parser/parse_expr.c | 468 +++++++++- src/backend/parser/parse_target.c | 15 + src/backend/utils/adt/jsonb.c | 62 ++ src/backend/utils/adt/jsonpath_exec.c | 290 ++++-- src/backend/utils/adt/ruleutils.c | 119 +++ src/include/catalog/pg_proc.dat | 10 +- src/include/executor/execExpr.h | 52 ++ src/include/nodes/nodes.h | 3 + src/include/nodes/primnodes.h | 56 ++ src/include/utils/jsonb.h | 3 + src/include/utils/jsonpath.h | 100 +++ src/test/regress/expected/json_sqljson.out | 15 + src/test/regress/expected/jsonb_sqljson.out | 824 ++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 2 + src/test/regress/sql/json_sqljson.sql | 11 + src/test/regress/sql/jsonb_sqljson.sql | 250 ++++++ 28 files changed, 3299 insertions(+), 87 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/sql/json_sqljson.sql create mode 100644 src/test/regress/sql/jsonb_sqljson.sql diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 3b220574d5..bd5f7ae1df 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2919,6 +2919,18 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) APP_JUMB(opts->value_type); } break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + APP_JUMB(jexpr->op); + JumbleExpr(jstate, jexpr->raw_expr); + JumbleExpr(jstate, (Node *) jexpr->path_spec); + JumbleExpr(jstate, (Node *) jexpr->passing.values); + JumbleExpr(jstate, jexpr->on_empty.default_expr); + JumbleExpr(jstate, jexpr->on_error.default_expr); + } + break; case T_List: foreach(temp, (List *) node) { diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 446f99416b..e8b7e07c67 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -44,6 +44,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -2111,6 +2112,92 @@ ExecInitExprRec(Expr *node, ExprState *state, resnull); 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.raw_expr = + palloc(sizeof(*scratch.d.jsonexpr.raw_expr)); + + ExecInitExprRec((Expr *) jexpr->raw_expr, state, + &scratch.d.jsonexpr.raw_expr->value, + &scratch.d.jsonexpr.raw_expr->isnull); + + scratch.d.jsonexpr.formatted_expr = + ExecInitExpr((Expr *) jexpr->formatted_expr, state->parent); + + scratch.d.jsonexpr.result_expr = jexpr->result_coercion + ? ExecInitExpr((Expr *) jexpr->result_coercion->expr, + state->parent) + : NULL; + + scratch.d.jsonexpr.default_on_empty = + 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); + Value *argname = (Value *) lfirst(argnamelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->name = pstrdup(argname->val.str); + 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); + } + + if (jexpr->coercions) + { + JsonCoercion **coercion; + struct JsonCoercionState *cstate; + + for (cstate = &scratch.d.jsonexpr.coercions.null, + coercion = &jexpr->coercions->null; + coercion <= &jexpr->coercions->composite; + coercion++, cstate++) + { + cstate->coercion = *coercion; + cstate->estate = *coercion ? + ExecInitExpr((Expr *)(*coercion)->expr, + state->parent) : 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 66a67c72b2..76664f6f4b 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -64,13 +64,17 @@ #include "funcapi.h" #include "utils/memutils.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/builtins.h" #include "utils/date.h" #include "utils/datum.h" #include "utils/expandedrecord.h" +#include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" #include "utils/typcache.h" @@ -385,6 +389,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_WINDOW_FUNC, &&CASE_EEOP_SUBPLAN, &&CASE_EEOP_ALTERNATIVE_SUBPLAN, + &&CASE_EEOP_JSONEXPR, &&CASE_EEOP_AGG_STRICT_DESERIALIZE, &&CASE_EEOP_AGG_DESERIALIZE, &&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS, @@ -1731,7 +1736,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { /* too complex for an inline implementation */ ExecEvalAggOrderedTransTuple(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_JSONEXPR) + { + /* too complex for an inline implementation */ + ExecEvalJson(state, op, econtext); EEO_NEXT(); } @@ -4142,3 +4153,425 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op, ExecStoreVirtualTuple(pertrans->sortslot); tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot); } + +/* + * Evaluate a expression substituting specified value in its CaseTestExpr nodes. + */ +static Datum +ExecEvalExprPassingCaseValue(ExprState *estate, ExprContext *econtext, + bool *isnull, + Datum caseval_datum, bool caseval_isnull) +{ + Datum res; + Datum save_datum = econtext->caseValue_datum; + bool save_isNull = econtext->caseValue_isNull; + + econtext->caseValue_datum = caseval_datum; + econtext->caseValue_isNull = caseval_isnull; + + PG_TRY(); + { + res = ExecEvalExpr(estate, econtext, isnull); + } + PG_CATCH(); + { + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + PG_RE_THROW(); + } + PG_END_TRY(); + + econtext->caseValue_datum = save_datum; + econtext->caseValue_isNull = save_isNull; + + return res; +} + +/* + * 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) +{ + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + JsonCoercion *coercion = jexpr->result_coercion; + 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); + + res = InputFunctionCall(&op->d.jsonexpr.input.func, str, + op->d.jsonexpr.input.typioparam, + jexpr->returning.typmod); + } + else if (op->d.jsonexpr.result_expr) + res = ExecEvalExprPassingCaseValue(op->d.jsonexpr.result_expr, econtext, + isNull, res, *isNull); + /* else no coercion, simply return item */ + + return res; +} + +/* + * Evaluate a JSON path variable caching computed value. + */ +int +EvalJsonPathVar(void *cxt, bool isJsonb, char *varName, int varNameLen, + JsonItem *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) + { + JsonItemGetType(val) = jbvNull; + return 0; + } + + JsonItemFromDatum(var->value, var->typid, var->typmod, val, isJsonb); + + *baseObject = *JsonItemJbv(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(JsonItem *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pcoercion) +{ + struct JsonCoercionState *coercion; + Datum res; + JsonItem buf; + + if (JsonItemIsBinary(item) && + JsonContainerIsScalar(JsonItemBinary(item).data)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(JsonItemBinary(item).data, JsonItemJbv(&buf)); + item = &buf; + Assert(res); + } + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (JsonItemGetType(item)) + { + case jbvNull: + coercion = &coercions->null; + res = (Datum) 0; + break; + + case jbvString: + coercion = &coercions->string; + res = PointerGetDatum( + cstring_to_text_with_len(JsonItemString(item).val, + JsonItemString(item).len)); + break; + + case jbvNumeric: + coercion = &coercions->numeric; + res = JsonItemNumericDatum(item); + break; + + case jbvBool: + coercion = &coercions->boolean; + res = JsonItemBool(item); + break; + + case jsiDatetime: + res = JsonItemDatetime(item).value; + switch (JsonItemDatetime(item).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", + JsonItemDatetime(item).typid); + return (Datum) 0; + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + coercion = &coercions->composite; + res = JsonbPGetDatum(JsonItemToJsonb(item)); + break; + + default: + elog(ERROR, "unexpected jsonb value type %d", JsonItemGetType(item)); + return (Datum) 0; + } + + *pcoercion = coercion; + + return res; +} + +static Datum +ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, + JsonExpr *jexpr, JsonPath *path, Datum item, bool *resnull) +{ + bool empty = false; + Datum res = (Datum) 0; + + if (op->d.jsonexpr.formatted_expr) + { + bool isnull; + + item = ExecEvalExprPassingCaseValue(op->d.jsonexpr.formatted_expr, + econtext, &isnull, item, false); + if (isnull) + { + /* execute domain checks for NULLs */ + (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull); + *resnull = true; + return (Datum) 0; + } + } + + switch (jexpr->op) + { + case IS_JSON_QUERY: + res = JsonbPathQuery(item, path, jexpr->wrapper, &empty, + op->d.jsonexpr.args); + *resnull = !DatumGetPointer(res); + break; + + case IS_JSON_VALUE: + { + JsonItem *jbv = JsonbPathValue(item, path, &empty, + op->d.jsonexpr.args); + struct JsonCoercionState *jcstate; + + if (!jbv) + break; + + *resnull = false; + + res = ExecPrepareJsonItemCoercion(jbv, + &op->d.jsonexpr.jsexpr->returning, + &op->d.jsonexpr.coercions, + &jcstate); + + /* coerce item datum to the output type */ + if ((jcstate->coercion && + (jcstate->coercion->via_io || + jcstate->coercion->via_populate)) || /* ignored for scalars jsons */ + jexpr->returning.typid == JSONOID || + jexpr->returning.typid == JSONBOID) + { + /* use coercion via I/O from json[b] to the output type */ + res = JsonbPGetDatum(JsonItemToJsonb(jbv)); + res = ExecEvalJsonExprCoercion(op, econtext, res, resnull); + } + else if (jcstate->estate) + { + res = ExecEvalExprPassingCaseValue(jcstate->estate, + econtext, + resnull, + res, false); + } + /* else no coercion */ + } + break; + + case IS_JSON_EXISTS: + res = BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); + *resnull = false; + break; + + default: + elog(ERROR, "unrecognized SQL/JSON expression op %d", + jexpr->op); + return (Datum) 0; + } + + if (empty) + { + if (jexpr->on_empty.btype == JSON_BEHAVIOR_ERROR) + ereport(ERROR, + (errcode(ERRCODE_NO_JSON_ITEM), + errmsg("no SQL/JSON item"))); + + /* execute ON EMPTY behavior */ + res = ExecEvalJsonBehavior(econtext, &jexpr->on_empty, + op->d.jsonexpr.default_on_empty, resnull); + } + + if (jexpr->op != IS_JSON_EXISTS && + (!empty ? jexpr->op != IS_JSON_VALUE : + /* result is already coerced in DEFAULT behavior case */ + jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT)) + res = ExecEvalJsonExprCoercion(op, econtext, res, resnull); + + return res; +} + +/* ---------------------------------------------------------------- + * ExecEvalJson + * ---------------------------------------------------------------- + */ +void +ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + Datum item; + Datum res = (Datum) 0; + JsonPath *path; + ListCell *lc; + + *op->resnull = true; /* until we get a result */ + *op->resvalue = (Datum) 0; + + if (op->d.jsonexpr.raw_expr->isnull) + { + /* execute domain checks for NULLs */ + (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + + Assert(*op->resnull); + *op->resnull = true; + + return; + } + + item = op->d.jsonexpr.raw_expr->value; + + path = DatumGetJsonPathP(jexpr->path_spec->constvalue); + + /* reset JSON path variable contexts */ + foreach(lc, op->d.jsonexpr.args) + { + JsonPathVariableEvalContext *var = lfirst(lc); + + var->econtext = econtext; + var->evaluated = false; + } + + if (jexpr->on_error.btype == JSON_BEHAVIOR_ERROR) + { + /* No need to use PG_TRY/PG_CATCH with subtransactions. */ + res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + op->resnull); + } + else + { + MemoryContext oldcontext = CurrentMemoryContext; + + PG_TRY(); + { + res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + op->resnull); + } + PG_CATCH(); + { + ErrorData *edata; + + /* Save error info in oldcontext */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != ERRCODE_DATA_EXCEPTION) + ReThrowError(edata); + + /* Execute ON ERROR behavior. */ + res = ExecEvalJsonBehavior(econtext, &jexpr->on_error, + op->d.jsonexpr.default_on_error, + op->resnull); + + if (jexpr->op != IS_JSON_EXISTS && + /* result is already coerced in DEFAULT behavior case */ + jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT) + res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + } + PG_END_TRY(); + } + + *op->resvalue = res; +} diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 30133634c7..c925f8eb9a 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2431,6 +2431,13 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[i + 1]); break; + + case EEOP_JSONEXPR: + build_EvalXFunc(b, mod, "ExecEvalJson", + v_state, v_econtext, op); + LLVMBuildBr(b, opblocks[i + 1]); + break; + case EEOP_LAST: Assert(false); break; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 43117e89da..8d6c8b868c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2357,6 +2357,94 @@ _copyJsonArrayQueryCtor(const JsonArrayQueryCtor *from) return newnode; } +/* + * _copyJsonExpr + */ +static JsonExpr * +_copyJsonExpr(const JsonExpr *from) +{ + JsonExpr *newnode = makeNode(JsonExpr); + + COPY_SCALAR_FIELD(op); + COPY_NODE_FIELD(raw_expr); + COPY_NODE_FIELD(formatted_expr); + COPY_NODE_FIELD(result_coercion); + COPY_SCALAR_FIELD(format); + COPY_NODE_FIELD(path_spec); + COPY_NODE_FIELD(passing.values); + COPY_NODE_FIELD(passing.names); + COPY_SCALAR_FIELD(returning); + COPY_SCALAR_FIELD(on_error); + COPY_NODE_FIELD(on_error.default_expr); + COPY_SCALAR_FIELD(on_empty); + COPY_NODE_FIELD(on_empty.default_expr); + 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; +} + +/* + * _copylJsonItemCoercions + */ +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 */ @@ -2387,6 +2475,51 @@ _copyJsonIsPredicateOpts(const JsonIsPredicateOpts *from) 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_STRING_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 * @@ -5321,6 +5454,27 @@ copyObjectImpl(const void *from) case T_JsonIsPredicateOpts: retval = _copyJsonIsPredicateOpts(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 d864bea650..8016c1c758 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -855,6 +855,73 @@ _equalJsonIsPredicateOpts(const JsonIsPredicateOpts *a, return true; } +/* + * _equalJsonExpr + */ +static bool +_equalJsonExpr(const JsonExpr *a, const JsonExpr *b) +{ + COMPARE_SCALAR_FIELD(op); + COMPARE_NODE_FIELD(raw_expr); + COMPARE_NODE_FIELD(formatted_expr); + COMPARE_NODE_FIELD(result_coercion); + COMPARE_SCALAR_FIELD(format.type); + COMPARE_SCALAR_FIELD(format.encoding); + COMPARE_LOCATION_FIELD(format.location); + COMPARE_NODE_FIELD(path_spec); + COMPARE_NODE_FIELD(passing.values); + COMPARE_NODE_FIELD(passing.names); + COMPARE_SCALAR_FIELD(returning.format.type); + COMPARE_SCALAR_FIELD(returning.format.encoding); + COMPARE_LOCATION_FIELD(returning.format.location); + COMPARE_SCALAR_FIELD(returning.typid); + COMPARE_SCALAR_FIELD(returning.typmod); + COMPARE_SCALAR_FIELD(on_error.btype); + COMPARE_NODE_FIELD(on_error.default_expr); + COMPARE_SCALAR_FIELD(on_empty.btype); + COMPARE_NODE_FIELD(on_empty.default_expr); + 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 */ @@ -3225,6 +3292,15 @@ equal(const void *a, const void *b) case T_JsonIsPredicateOpts: retval = _equalJsonIsPredicateOpts(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 diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 39ff235b77..7e0fa5006a 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -262,6 +262,12 @@ exprType(const Node *expr) case T_JsonValueExpr: type = exprType((Node *) ((const JsonValueExpr *) expr)->expr); 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 */ @@ -497,6 +503,10 @@ exprTypmod(const Node *expr) return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); case T_JsonValueExpr: return exprTypmod((Node *) ((const JsonValueExpr *) expr)->expr); + case T_JsonExpr: + return ((JsonExpr *) expr)->returning.typmod; + case T_JsonCoercion: + return exprTypmod(((const JsonCoercion *) expr)->expr); default: break; } @@ -915,6 +925,21 @@ exprCollation(const Node *expr) case T_JsonValueExpr: coll = exprCollation((Node *) ((const JsonValueExpr *) expr)->expr); 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 */ @@ -1122,6 +1147,21 @@ exprSetCollation(Node *expr, Oid collation) exprSetCollation((Node *) ((const JsonValueExpr *) expr)->expr, collation); 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; @@ -1565,6 +1605,15 @@ exprLocation(const Node *expr) case T_JsonValueExpr: loc = exprLocation((Node *) ((const JsonValueExpr *) expr)->expr); break; + case T_JsonExpr: + { + const JsonExpr *jsexpr = (const JsonExpr *) expr; + + /* consider both function name and leftmost arg */ + loc = leftmostLoc(jsexpr->location, + exprLocation(jsexpr->raw_expr)); + } + break; default: /* for any other node type it's just unknown... */ loc = -1; @@ -2264,6 +2313,55 @@ expression_tree_walker(Node *node, break; case T_JsonValueExpr: return walker(((JsonValueExpr *) node)->expr, context); + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + + if (walker(jexpr->raw_expr, context)) + return true; + 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 (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)); @@ -3126,6 +3224,54 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } + break; + case T_JsonExpr: + { + JsonExpr *jexpr = (JsonExpr *) node; + JsonExpr *newnode; + + FLATCOPY(newnode, jexpr, JsonExpr); + MUTATE(newnode->raw_expr, jexpr->raw_expr, 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 */ + 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)); @@ -3849,6 +3995,41 @@ raw_expression_tree_walker(Node *node, 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->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 02f287f998..f39624303b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1711,6 +1711,64 @@ _outJsonCtorOpts(StringInfo str, const JsonCtorOpts *node) WRITE_BOOL_FIELD(absent_on_null); } +static void +_outJsonExpr(StringInfo str, const JsonExpr *node) +{ + WRITE_NODE_TYPE("JSONEXPR"); + + WRITE_ENUM_FIELD(op, JsonExprOp); + WRITE_NODE_FIELD(raw_expr); + WRITE_NODE_FIELD(formatted_expr); + WRITE_NODE_FIELD(result_coercion); + WRITE_ENUM_FIELD(format.type, JsonFormatType); + WRITE_ENUM_FIELD(format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(format.location); + WRITE_NODE_FIELD(path_spec); + WRITE_NODE_FIELD(passing.values); + WRITE_NODE_FIELD(passing.names); + WRITE_ENUM_FIELD(returning.format.type, JsonFormatType); + WRITE_ENUM_FIELD(returning.format.encoding, JsonEncoding); + WRITE_LOCATION_FIELD(returning.format.location); + WRITE_OID_FIELD(returning.typid); + WRITE_INT_FIELD(returning.typmod); + WRITE_ENUM_FIELD(on_error.btype, JsonBehaviorType); + WRITE_NODE_FIELD(on_error.default_expr); + WRITE_ENUM_FIELD(on_empty.btype, JsonBehaviorType); + WRITE_NODE_FIELD(on_empty.default_expr); + 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); +} + static void _outJsonIsPredicateOpts(StringInfo str, const JsonIsPredicateOpts *node) { @@ -4323,6 +4381,15 @@ outNode(StringInfo str, const void *obj) case T_JsonIsPredicateOpts: _outJsonIsPredicateOpts(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 5a2772586d..d7075e735b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1371,7 +1371,6 @@ static JsonCtorOpts * _readJsonCtorOpts(void) { READ_LOCALS(JsonCtorOpts); - READ_ENUM_FIELD(returning.format.type, JsonFormatType); READ_ENUM_FIELD(returning.format.encoding, JsonEncoding); READ_LOCATION_FIELD(returning.format.location); @@ -1383,6 +1382,79 @@ _readJsonCtorOpts(void) READ_DONE(); } +/* + * _readJsonExpr + */ +static JsonExpr * +_readJsonExpr(void) +{ + READ_LOCALS(JsonExpr); + + READ_ENUM_FIELD(op, JsonExprOp); + READ_NODE_FIELD(raw_expr); + READ_NODE_FIELD(formatted_expr); + READ_NODE_FIELD(result_coercion); + READ_ENUM_FIELD(format.type, JsonFormatType); + READ_ENUM_FIELD(format.encoding, JsonEncoding); + READ_LOCATION_FIELD(format.location); + READ_NODE_FIELD(path_spec); + READ_NODE_FIELD(passing.values); + READ_NODE_FIELD(passing.names); + READ_ENUM_FIELD(returning.format.type, JsonFormatType); + READ_ENUM_FIELD(returning.format.encoding, JsonEncoding); + READ_LOCATION_FIELD(returning.format.location); + READ_OID_FIELD(returning.typid); + READ_INT_FIELD(returning.typmod); + READ_ENUM_FIELD(on_error.btype, JsonBehaviorType); + READ_NODE_FIELD(on_error.default_expr); + READ_ENUM_FIELD(on_empty.btype, JsonBehaviorType); + READ_NODE_FIELD(on_empty.default_expr); + 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(); +} + /* * _readJsonIsPredicateOpts */ @@ -2866,6 +2938,12 @@ parseNodeString(void) return_value = _readJsonCtorOpts(); else if (MATCH("JSONISOPTS", 10)) return_value = _readJsonIsPredicateOpts(); + 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 a2a9b1f7be..7da18f5cc8 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -4022,7 +4022,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/parser/parse_collate.c b/src/backend/parser/parse_collate.c index 4d2e586bd6..c20ac2b5a9 100644 --- a/src/backend/parser/parse_collate.c +++ b/src/backend/parser/parse_collate.c @@ -667,6 +667,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 a65d1f0bdf..f2959cba9e 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -130,6 +130,8 @@ static Node *transformJsonArrayQueryCtor(ParseState *pstate, 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 *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -402,6 +404,14 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); break; + case T_JsonFuncExpr: + result = transformJsonFuncExpr(pstate, (JsonFuncExpr *) expr); + break; + + case T_JsonValueExpr: + result = transformJsonValueExpr(pstate, (JsonValueExpr *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3664,13 +3674,26 @@ makeJsonByteaToTextConversion(Node *expr, JsonFormat *format, int location) return (Node *) fexpr; } +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 * -transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, - JsonFormatType default_format) +transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, + JsonFormatType default_format, bool isarg, + Node **rawexpr) { Node *expr = transformExprRecurse(pstate, (Node *) ve->expr); JsonFormatType format; @@ -3687,6 +3710,17 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, get_type_category_preferred(exprtype, &typcategory, &typispreferred); + if (rawexpr) + { + /* + * Save a raw context item expression if it is needed for the isolation + * of error handling in the formatting stage. + */ + *rawexpr = expr; + assign_expr_collations(pstate, expr); + expr = makeCaseTestExpr(expr); + } + if (ve->format.type != JS_FORMAT_DEFAULT) { if (ve->format.encoding != JS_ENC_DEFAULT && exprtype != BYTEAOID) @@ -3704,6 +3738,36 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, else format = ve->format.type; } + 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 @@ -3715,7 +3779,7 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *ve, Node *coerced; FuncExpr *fexpr; - if (exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) + if (!isarg && exprtype != BYTEAOID && typcategory != TYPCATEGORY_STRING) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg(ve->format.type == JS_FORMAT_DEFAULT ? @@ -3762,6 +3826,24 @@ transformJsonValueExpr(ParseState *pstate, JsonValueExpr *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, NULL); +} + +/* + * Transform JSON value expression using unspecified format by default. + */ +static Node * +transformJsonValueExprDefault(ParseState *pstate, JsonValueExpr *jve) +{ + return transformJsonValueExprExt(pstate, jve, JS_FORMAT_DEFAULT, false, NULL); +} + /* * Checks specified output format for its applicability to the target type. */ @@ -3949,8 +4031,7 @@ transformJsonObjectCtor(ParseState *pstate, JsonObjectCtor *ctor) { JsonKeyValue *kv = castNode(JsonKeyValue, lfirst(lc)); Node *key = transformExprRecurse(pstate, (Node *) kv->key); - Node *val = transformJsonValueExpr(pstate, kv->value, - JS_FORMAT_DEFAULT); + Node *val = transformJsonValueExprDefault(pstate, kv->value); args = lappend(args, key); args = lappend(args, val); @@ -4145,7 +4226,7 @@ transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg) transformJsonOutput(pstate, agg->ctor.output, true, &returning); key = transformExprRecurse(pstate, (Node *) agg->arg->key); - val = transformJsonValueExpr(pstate, agg->arg->value, JS_FORMAT_DEFAULT); + val = transformJsonValueExprDefault(pstate, agg->arg->value); args = list_make4(key, val, @@ -4187,7 +4268,7 @@ transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg) transformJsonOutput(pstate, agg->ctor.output, true, &returning); - arg = transformJsonValueExpr(pstate, agg->arg, JS_FORMAT_DEFAULT); + arg = transformJsonValueExprDefault(pstate, agg->arg); if (returning.format.type == JS_FORMAT_JSONB) { @@ -4238,8 +4319,7 @@ transformJsonArrayCtor(ParseState *pstate, JsonArrayCtor *ctor) foreach(lc, ctor->exprs) { JsonValueExpr *jsval = castNode(JsonValueExpr, lfirst(lc)); - Node *val = transformJsonValueExpr(pstate, jsval, - JS_FORMAT_DEFAULT); + Node *val = transformJsonValueExprDefault(pstate, jsval); args = lappend(args, val); } @@ -4372,3 +4452,373 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) return (Node *) fexpr; } + +/* + * Transform a JSON PASSING clause. + */ +static void +transformJsonPassingArgs(ParseState *pstate, JsonFormatType format, List *args, + JsonPassing *passing) +{ + 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, NULL); + + 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) +{ + JsonBehavior b; + + b.btype = behavior ? behavior->btype : default_behavior; + b.default_expr = b.btype != JSON_BEHAVIOR_DEFAULT ? NULL : + transformExprRecurse(pstate, behavior->default_expr); + + return b; +} + +/* + * 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); + Datum jsonpath; + 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 = transformJsonValueExprExt(pstate, + func->common->expr, + JS_FORMAT_JSON, + false, + &jsexpr->raw_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; + + if (jsexpr->formatted_expr == jsexpr->raw_expr) + jsexpr->formatted_expr = NULL; + + jsexpr->result_coercion = NULL; + jsexpr->omit_quotes = false; + + jsexpr->format = func->common->expr->format; + + /* parse JSON path string */ + jsonpath = DirectFunctionCall1(jsonpath_in, + CStringGetDatum(func->common->pathspec)); + + jsexpr->path_spec = makeConst(JSONPATHOID, -1, InvalidOid, -1, + jsonpath, false, false); + + /* transform and coerce to json[b] passing arguments */ + transformJsonPassingArgs(pstate, format, func->common->passing, + &jsexpr->passing); + + 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 = *context_format; + + if (ret->format.type == JS_FORMAT_DEFAULT) + is_jsonb = exprType(context_item) == JSONBOID; + else + is_jsonb = ret->format.type == 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, 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, JSON_QUERY, JSON_EXISTS. + */ +static void +transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func, + JsonExpr *jsexpr) +{ + Node *expr = jsexpr->formatted_expr ? + jsexpr->formatted_expr : jsexpr->raw_expr; + + transformJsonOutput(pstate, func->output, false, &jsexpr->returning); + + /* 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->raw_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->raw_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_INTERNAL_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, 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, + 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); + Node *contextItemExpr = + jsexpr->formatted_expr ? jsexpr->formatted_expr : jsexpr->raw_expr; + const char *func_name = NULL; + + switch (func->op) + { + case IS_JSON_VALUE: + func_name = "JSON_VALUE"; + + transformJsonFuncExprOutput(pstate, func, jsexpr); + + jsexpr->returning.format.type = 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->wrapper = func->wrapper; + jsexpr->omit_quotes = func->omit_quotes; + + break; + + case IS_JSON_EXISTS: + func_name = "JSON_EXISTS"; + + jsexpr->returning.format.type = JS_FORMAT_DEFAULT; + jsexpr->returning.format.encoding = JS_ENC_DEFAULT; + jsexpr->returning.format.location = -1; + jsexpr->returning.typid = BOOLOID; + jsexpr->returning.typmod = -1; + + 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; +} diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index a06c5cb668..62b9713a3c 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1939,6 +1939,21 @@ FigureColnameInternal(Node *node, char **name) 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/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index d6917b4161..686a44aeb2 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2352,3 +2352,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/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 86cbf489d1..ce3ee02857 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -79,72 +79,6 @@ #include "utils/varlena.h" -typedef enum JsonItemType -{ - /* Scalar types */ - jsiNull = jbvNull, - jsiString = jbvString, - jsiNumeric = jbvNumeric, - jsiBool = jbvBool, - /* Composite types */ - jsiArray = jbvArray, - jsiObject = jbvObject, - /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ - jsiBinary = jbvBinary, - - /* - * Virtual types. - * - * These types are used only for in-memory JSON processing and serialized - * into JSON strings when outputted to json/jsonb. - */ - jsiDatetime = 0x20 -} JsonItemType; - -/* SQL/JSON item */ -typedef struct JsonItem -{ - struct JsonItem *next; - - union - { - int type; /* XXX JsonItemType */ - - JsonbValue jbv; - - struct - { - int type; - Datum value; - Oid typid; - int32 typmod; - int tz; - } datetime; - } val; -} JsonItem; - -#define JsonItemJbv(jsi) (&(jsi)->val.jbv) -#define JsonItemBool(jsi) (JsonItemJbv(jsi)->val.boolean) -#define JsonItemNumeric(jsi) (JsonItemJbv(jsi)->val.numeric) -#define JsonItemNumericDatum(jsi) NumericGetDatum(JsonItemNumeric(jsi)) -#define JsonItemString(jsi) (JsonItemJbv(jsi)->val.string) -#define JsonItemBinary(jsi) (JsonItemJbv(jsi)->val.binary) -#define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array) -#define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object) -#define JsonItemDatetime(jsi) ((jsi)->val.datetime) - -#define JsonItemGetType(jsi) ((jsi)->val.type) -#define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull) -#define JsonItemIsBool(jsi) (JsonItemGetType(jsi) == jsiBool) -#define JsonItemIsNumeric(jsi) (JsonItemGetType(jsi) == jsiNumeric) -#define JsonItemIsString(jsi) (JsonItemGetType(jsi) == jsiString) -#define JsonItemIsBinary(jsi) (JsonItemGetType(jsi) == jsiBinary) -#define JsonItemIsArray(jsi) (JsonItemGetType(jsi) == jsiArray) -#define JsonItemIsObject(jsi) (JsonItemGetType(jsi) == jsiObject) -#define JsonItemIsDatetime(jsi) (JsonItemGetType(jsi) == jsiDatetime) -#define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \ - JsonItemIsDatetime(jsi)) - typedef union Jsonx { Jsonb jb; @@ -395,7 +329,6 @@ static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, static JsonItem *copyJsonItem(JsonItem *src); static JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); -static Jsonb *JsonItemToJsonb(JsonItem *jsi); static const char *JsonItemTypeName(JsonItem *jsi); static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, @@ -428,7 +361,6 @@ static void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, static JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); -static Json *JsonItemToJson(JsonItem *jsi); static Jsonx *JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb); static Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb); static Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb); @@ -2712,7 +2644,7 @@ JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) } } -static Jsonb * +Jsonb * JsonItemToJsonb(JsonItem *jsi) { JsonbValue jbv; @@ -3044,7 +2976,7 @@ JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested) JsonIteratorNext(&it->it.js, jbv, skipNested); } -static Json * +Json * JsonItemToJson(JsonItem *jsi) { JsonbValue jbv; @@ -3399,3 +3331,221 @@ JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) JsonItemDatetime(item).typmod = typmod; JsonItemDatetime(item).tz = tz; } + +/********************Interface to pgsql's executor***************************/ + +bool +JsonbPathExists(Datum jb, JsonPath *jp, List *vars) +{ + JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar, + (Jsonx *) DatumGetJsonbP(jb), + true, true, NULL); + + Assert(!jperIsError(res)); + + return res == jperOk; +} + +Datum +JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars) +{ + JsonItem *first; + bool wrap; + JsonValueList found = {0}; + JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + int count; + + jper = executeJsonPath(jp, vars, EvalJsonPathVar, + (Jsonx *) DatumGetJsonbP(jb), true, true, &found); + Assert(!jperIsError(jper)); + + 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 || + JsonItemIsScalar(first) || + (JsonItemIsBinary(first) && + JsonContainerIsScalar(JsonItemBinary(first).data)); + else + { + elog(ERROR, "unrecognized json wrapper %d", wrapper); + wrap = false; + } + + if (wrap) + return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found, true))); + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_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(JsonItemToJsonb(first)); + + *empty = true; + return PointerGetDatum(NULL); +} + +JsonItem * +JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) +{ + JsonItem *res; + JsonValueList found = { 0 }; + JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + int count; + + jper = executeJsonPath(jp, vars, EvalJsonPathVar, + (Jsonx *) DatumGetJsonbP(jb), true, true, &found); + Assert(!jperIsError(jper)); + + count = JsonValueListLength(&found); + + *empty = !count; + + if (*empty) + return NULL; + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), + errmsg("JSON path expression in JSON_VALUE should return " + "singleton scalar item"))); + + res = JsonValueListHead(&found); + + if (JsonItemIsBinary(res) && + JsonContainerIsScalar(JsonItemBinary(res).data)) + JsonbExtractScalar(JsonItemBinary(res).data, JsonItemJbv(res)); + + if (!JsonItemIsScalar(res)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg("JSON path expression in JSON_VALUE should return " + "singleton scalar item"))); + + if (JsonItemIsNull(res)) + return NULL; + + return res; +} + +void +JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonItem *res, + bool isJsonb) +{ + switch (typid) + { + case BOOLOID: + JsonItemInitBool(res, DatumGetBool(val)); + break; + case NUMERICOID: + JsonItemInitNumericDatum(res, val); + break; + case INT2OID: + JsonItemInitNumericDatum(res, DirectFunctionCall1(int2_numeric, val)); + break; + case INT4OID: + JsonItemInitNumericDatum(res, DirectFunctionCall1(int4_numeric, val)); + break; + case INT8OID: + JsonItemInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val)); + break; + case FLOAT4OID: + JsonItemInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val)); + break; + case FLOAT8OID: + JsonItemInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val)); + break; + case TEXTOID: + case VARCHAROID: + JsonItemInitString(res, VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val)); + break; + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + JsonItemInitDatetime(res, val, typid, typmod, 0); + break; + case JSONBOID: + { + JsonbValue *jbv = JsonItemJbv(res); + Jsonb *jb = DatumGetJsonbP(val); + + if (JsonContainerIsScalar(&jb->root)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonbExtractScalar(&jb->root, jbv); + Assert(res); + } + else if (isJsonb) + { + JsonbInitBinary(jbv, jb); + } + else + { + StringInfoData buf; + text *txt; + Json *js; + + initStringInfo(&buf); + JsonbToCString(&buf, &jb->root, VARSIZE(jb)); + txt = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + + js = JsonCreate(txt); + + JsonInitBinary(jbv, js); + } + break; + } + case JSONOID: + { + JsonbValue *jbv = JsonItemJbv(res); + Json *js = DatumGetJsonP(val); + + if (JsonContainerIsScalar(&js->root)) + { + bool res PG_USED_FOR_ASSERTS_ONLY; + + res = JsonExtractScalar(&js->root, jbv); + Assert(res); + } + else if (isJsonb) + { + text *txt = DatumGetTextP(val); + char *str = text_to_cstring(txt); + Jsonb *jb = + DatumGetJsonbP(DirectFunctionCall1(jsonb_in, CStringGetDatum(str))); + + pfree(str); + + JsonbInitBinary(jbv, jb); + } + else + { + JsonInitBinary(jbv, js); + } + 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 00a0bb9201..4c3c26c249 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7538,6 +7538,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags) case T_Aggref: case T_WindowFunc: case T_FuncExpr: + case T_JsonExpr: /* function-like: name(..) or name[..] */ return true; @@ -7656,6 +7657,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; @@ -7879,6 +7881,54 @@ get_coercion(Expr *arg, deparse_context *context, bool showimplicit, } } +static void +get_json_behavior(JsonBehavior *behavior, deparse_context *context, + const char *on) +{ + switch (behavior->btype) + { + case JSON_BEHAVIOR_DEFAULT: + appendStringInfoString(context->buf, " DEFAULT "); + get_rule_expr(behavior->default_expr, context, false); + break; + + case JSON_BEHAVIOR_EMPTY: + appendStringInfoString(context->buf, " EMPTY"); + break; + + case JSON_BEHAVIOR_EMPTY_ARRAY: + appendStringInfoString(context->buf, " EMPTY ARRAY"); + break; + + case JSON_BEHAVIOR_EMPTY_OBJECT: + appendStringInfoString(context->buf, " EMPTY OBJECT"); + break; + + case JSON_BEHAVIOR_ERROR: + appendStringInfoString(context->buf, " ERROR"); + break; + + case JSON_BEHAVIOR_FALSE: + appendStringInfoString(context->buf, " FALSE"); + break; + + case JSON_BEHAVIOR_NULL: + appendStringInfoString(context->buf, " NULL"); + break; + + case JSON_BEHAVIOR_TRUE: + appendStringInfoString(context->buf, " TRUE"); + break; + + case JSON_BEHAVIOR_UNKNOWN: + appendStringInfoString(context->buf, " UNKNOWN"); + break; + } + + appendStringInfo(context->buf, " ON %s", on); +} + + /* ---------- * get_rule_expr - Parse back an expression * @@ -8995,6 +9045,7 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_JsonValueExpr: { JsonValueExpr *jve = (JsonValueExpr *) node; @@ -9004,6 +9055,73 @@ get_rule_expr(Node *node, deparse_context *context, } 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->raw_expr, context, showimplicit); + + get_json_format(&jexpr->format, context); + + appendStringInfoString(buf, ", "); + + get_const_expr(jexpr->path_spec, context, -1); + + 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", + ((Value *) lfirst(lc1))->val.str); + } + } + + if (jexpr->op != IS_JSON_EXISTS) + get_json_returning(&jexpr->returning, context, + jexpr->op != IS_JSON_VALUE); + + if (jexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(buf, " WITH CONDITIONAL WRAPPER"); + + if (jexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(buf, " WITH UNCONDITIONAL WRAPPER"); + + if (jexpr->omit_quotes) + appendStringInfo(buf, " OMIT QUOTES"); + + if (jexpr->op != IS_JSON_EXISTS) + get_json_behavior(&jexpr->on_empty, context, "EMPTY"); + + get_json_behavior(&jexpr->on_error, context, "ERROR"); + + appendStringInfoString(buf, ")"); + } + break; + case T_List: { char *sep; @@ -9100,6 +9218,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: diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index ae869b0c8e..e83fb25587 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8174,7 +8174,7 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, -{ oid => '3431', descr => 'json aggregate transition function', +{ oid => '4035', descr => 'json aggregate transition function', proname => 'json_agg_strict_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_strict_transfn' }, @@ -8185,7 +8185,7 @@ proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, -{ oid => '3431', descr => 'aggregate input into json', +{ oid => '3434', descr => 'aggregate input into json', proname => 'json_agg_strict', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, @@ -8205,7 +8205,7 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, -{ oid => '3430', descr => 'aggregate input into a json object', +{ oid => '3435', descr => 'aggregate input into a json object', proname => 'json_objectagg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any bool bool', prosrc => 'aggregate_dummy' }, @@ -8250,7 +8250,7 @@ { oid => '3261', descr => 'remove object fields with null values from json', proname => 'json_strip_nulls', prorettype => 'json', proargtypes => 'json', prosrc => 'json_strip_nulls' }, -{ oid => '6073', descr => 'check json value type and key uniqueness', +{ oid => '6070', descr => 'check json value type and key uniqueness', proname => 'json_is_valid', prorettype => 'bool', proargtypes => 'json text bool', prosrc => 'json_is_valid' }, { oid => '6074', @@ -9080,7 +9080,7 @@ proname => 'jsonb_object_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any', prosrc => 'jsonb_object_agg_transfn' }, -{ oid => '3433', descr => 'jsonb object aggregate transition function', +{ oid => '4142', descr => 'jsonb object aggregate transition function', proname => 'jsonb_objectagg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal any any bool bool', prosrc => 'jsonb_objectagg_transfn' }, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 8e7f7c3d13..64dff43d2f 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -20,6 +20,7 @@ /* forward references to avoid circularity */ struct ExprEvalStep; struct SubscriptingRefState; +struct JsonItem; /* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ /* expression's interpreter has been initialized */ @@ -219,6 +220,7 @@ typedef enum ExprEvalOp EEOP_WINDOW_FUNC, EEOP_SUBPLAN, EEOP_ALTERNATIVE_SUBPLAN, + EEOP_JSONEXPR, /* aggregation related nodes */ EEOP_AGG_STRICT_DESERIALIZE, @@ -654,6 +656,50 @@ typedef struct ExprEvalStep int transno; int setoff; } agg_trans; + + /* 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 */ + + struct + { + Datum value; + bool isnull; + } *raw_expr; /* raw context item value */ + + ExprState *formatted_expr; /* formatted context item */ + 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 */ + + 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; @@ -754,6 +800,12 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); +extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pjcstate); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 35918e501a..238df4ca79 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -196,6 +196,9 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, T_IntoClause, + T_JsonExpr, + T_JsonCoercion, + T_JsonItemCoercions, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index bccdfef7d6..7383e87607 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1297,6 +1297,62 @@ typedef struct JsonPassing List *names; /* parallel list of Value strings */ } JsonPassing; +/* + * 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 *raw_expr; /* raw context item expression */ + Node *formatted_expr; /* formatted context item expression */ + JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */ + JsonFormat format; /* context item format (JSON/JSONB) */ + Const *path_spec; /* JSON path specification */ + JsonPassing passing; /* PASSING clause arguments */ + 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/utils/jsonb.h b/src/include/utils/jsonb.h index b63b4258ed..d71e3f46b8 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -390,6 +390,9 @@ 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); diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index f6b17c8aa2..bf8d6539c9 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -16,7 +16,9 @@ #include "fmgr.h" #include "utils/jsonb.h" +#include "utils/jsonapi.h" #include "nodes/pg_list.h" +#include "nodes/primnodes.h" typedef struct { @@ -246,4 +248,102 @@ typedef struct JsonPathParseResult extern JsonPathParseResult *parsejsonpath(const char *str, int len); +/* + * 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; + +/* Type of SQL/JSON item */ +typedef enum JsonItemType +{ + /* Scalar types */ + jsiNull = jbvNull, + jsiString = jbvString, + jsiNumeric = jbvNumeric, + jsiBool = jbvBool, + /* Composite types */ + jsiArray = jbvArray, + jsiObject = jbvObject, + /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ + jsiBinary = jbvBinary, + + /* + * Virtual types. + * + * These types are used only for in-memory JSON processing and serialized + * into JSON strings when outputted to json/jsonb. + */ + jsiDatetime = 0x20 +} JsonItemType; + +/* SQL/JSON item */ +typedef struct JsonItem +{ + struct JsonItem *next; + + union + { + int type; /* XXX JsonItemType */ + + JsonbValue jbv; + + struct + { + int type; + Datum value; + Oid typid; + int32 typmod; + int tz; + } datetime; + } val; +} JsonItem; + +#define JsonItemJbv(jsi) (&(jsi)->val.jbv) +#define JsonItemBool(jsi) (JsonItemJbv(jsi)->val.boolean) +#define JsonItemNumeric(jsi) (JsonItemJbv(jsi)->val.numeric) +#define JsonItemNumericDatum(jsi) NumericGetDatum(JsonItemNumeric(jsi)) +#define JsonItemString(jsi) (JsonItemJbv(jsi)->val.string) +#define JsonItemBinary(jsi) (JsonItemJbv(jsi)->val.binary) +#define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array) +#define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object) +#define JsonItemDatetime(jsi) ((jsi)->val.datetime) + +#define JsonItemGetType(jsi) ((jsi)->val.type) +#define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull) +#define JsonItemIsBool(jsi) (JsonItemGetType(jsi) == jsiBool) +#define JsonItemIsNumeric(jsi) (JsonItemGetType(jsi) == jsiNumeric) +#define JsonItemIsString(jsi) (JsonItemGetType(jsi) == jsiString) +#define JsonItemIsBinary(jsi) (JsonItemGetType(jsi) == jsiBinary) +#define JsonItemIsArray(jsi) (JsonItemGetType(jsi) == jsiArray) +#define JsonItemIsObject(jsi) (JsonItemGetType(jsi) == jsiObject) +#define JsonItemIsDatetime(jsi) (JsonItemGetType(jsi) == jsiDatetime) +#define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \ + JsonItemIsDatetime(jsi)) + +extern Jsonb *JsonItemToJsonb(JsonItem *jsi); +extern Json *JsonItemToJson(JsonItem *jsi); +extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, + JsonItem *res, bool isJsonb); + +extern bool JsonbPathExists(Datum jb, JsonPath *path, List *vars); +extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars); +extern JsonItem *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, + List *vars); + +extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, + int varNameLen, JsonItem *val, JsonbValue *baseObject); + #endif 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_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out new file mode 100644 index 0000000000..476ab6e6f7 --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,824 @@ +-- 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) + +-- 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); + json_value +------------ + \x313233 +(1 row) + +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) + +-- 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 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 '[]', '$[*]' 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]', '$' 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) + +-- 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') +); +\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 NULL ON EMPTY NULL ON ERROR) +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 FALSE ON ERROR)) + "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 NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar) + +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 FALSE ON ERROR)) + ((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 NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)) +(5 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 NULL ON EMPTY NULL ON ERROR) +(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; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index ad3b3f1630..7c9643602f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -99,7 +99,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 json_jsonpath jsonb_jsonpath sqljson +test: json jsonb json_encoding jsonpath jsonpath_encoding json_jsonpath jsonb_jsonpath sqljson json_sqljson jsonb_sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index b814dd9b0b..e7d884a90d 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -168,6 +168,8 @@ test: jsonpath_encoding test: json_jsonpath test: jsonb_jsonpath test: sqljson +test: json_sqljson +test: jsonb_sqljson test: plancache test: limit test: plpgsql 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_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql new file mode 100644 index 0000000000..d7183f4de5 --- /dev/null +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -0,0 +1,250 @@ +-- 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); + +-- 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); + +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); + +-- 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 ARRAY ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR 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]', '$' 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; + +-- 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') +); + +\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; From b321bdfb20ef88c1c11e858d9b39ecaf3b123f51 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 11 May 2017 18:37:18 +0300 Subject: [PATCH 31/66] Add JSON_QUERY support for row, array and domain types --- src/backend/executor/execExpr.c | 2 + src/backend/executor/execExprInterp.c | 8 ++++ src/backend/utils/adt/jsonfuncs.c | 44 +++++++++++++++++++ src/include/executor/execExpr.h | 2 + src/include/utils/jsonapi.h | 4 ++ src/test/regress/expected/jsonb_sqljson.out | 47 +++++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 16 +++++++ 7 files changed, 123 insertions(+) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index e8b7e07c67..327befc7b2 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2177,6 +2177,8 @@ ExecInitExprRec(Expr *node, ExprState *state, lappend(scratch.d.jsonexpr.args, var); } + scratch.d.jsonexpr.cache = NULL; + if (jexpr->coercions) { JsonCoercion **coercion; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 76664f6f4b..babf8bf273 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -73,6 +73,7 @@ #include "utils/date.h" #include "utils/datum.h" #include "utils/expandedrecord.h" +#include "utils/jsonapi.h" #include "utils/jsonb.h" #include "utils/jsonpath.h" #include "utils/lsyscache.h" @@ -4249,6 +4250,13 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, else if (op->d.jsonexpr.result_expr) res = ExecEvalExprPassingCaseValue(op->d.jsonexpr.result_expr, econtext, isNull, res, *isNull); + else if (coercion && coercion->via_populate) + res = json_populate_type(res, JSONBOID, + jexpr->returning.typid, + jexpr->returning.typmod, + &op->d.jsonexpr.cache, + econtext->ecxt_per_query_memory, + isNull); /* else no coercion, simply return item */ return res; diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index fe351edb2b..bcf7b5c8d1 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -3050,6 +3050,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) { diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 64dff43d2f..06b6a39d7a 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -680,6 +680,8 @@ typedef struct ExprEvalStep ExprState *default_on_error; /* ON ERROR DEFAULT expression */ List *args; /* passing arguments */ + void *cache; /* cache for json_populate_type() */ + struct JsonCoercionsState { struct JsonCoercionState diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 8d391b2965..58d0b94b8c 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -217,6 +217,10 @@ extern text *transform_json_string_values(text *json, void *action_state, extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp); +extern Datum json_populate_type(Datum json_val, Oid json_type, + Oid typid, int32 typmod, + void **cache, MemoryContext mcxt, bool *isnull); + extern Json *JsonCreate(text *json); extern JsonbIteratorToken JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested); diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index 476ab6e6f7..f03cdf4cc6 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -752,6 +752,53 @@ FROM 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 constraints CREATE TABLE test_jsonb_constraints ( js text, diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index d7183f4de5..e95d3cf20a 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -213,6 +213,22 @@ 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 constraints CREATE TABLE test_jsonb_constraints ( From 39b5797c80170c1555b1ce01cc8e698709f8ec1b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 3 Nov 2017 14:15:51 +0300 Subject: [PATCH 32/66] Allow variable jsonpath specifications --- .../pg_stat_statements/pg_stat_statements.c | 2 +- src/backend/executor/execExpr.c | 7 ++++ src/backend/executor/execExprInterp.c | 5 ++- src/backend/nodes/copyfuncs.c | 2 +- src/backend/nodes/nodeFuncs.c | 3 ++ src/backend/parser/gram.y | 7 ++-- src/backend/parser/parse_expr.c | 19 +++++++---- src/backend/utils/adt/ruleutils.c | 17 +++++++++- src/include/executor/execExpr.h | 3 +- src/include/nodes/parsenodes.h | 2 +- src/include/nodes/primnodes.h | 2 +- src/test/regress/expected/jsonb_sqljson.out | 34 +++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 9 +++++ 13 files changed, 94 insertions(+), 18 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index bd5f7ae1df..206deff18a 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2925,7 +2925,7 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) APP_JUMB(jexpr->op); JumbleExpr(jstate, jexpr->raw_expr); - JumbleExpr(jstate, (Node *) jexpr->path_spec); + JumbleExpr(jstate, jexpr->path_spec); JumbleExpr(jstate, (Node *) jexpr->passing.values); JumbleExpr(jstate, jexpr->on_empty.default_expr); JumbleExpr(jstate, jexpr->on_error.default_expr); diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 327befc7b2..caf87c7b08 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2128,6 +2128,13 @@ ExecInitExprRec(Expr *node, ExprState *state, &scratch.d.jsonexpr.raw_expr->value, &scratch.d.jsonexpr.raw_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.formatted_expr = ExecInitExpr((Expr *) jexpr->formatted_expr, state->parent); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index babf8bf273..30c1eaac8c 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4517,7 +4517,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) *op->resnull = true; /* until we get a result */ *op->resvalue = (Datum) 0; - if (op->d.jsonexpr.raw_expr->isnull) + if (op->d.jsonexpr.raw_expr->isnull || op->d.jsonexpr.pathspec->isnull) { /* execute domain checks for NULLs */ (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); @@ -4529,8 +4529,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) } item = op->d.jsonexpr.raw_expr->value; - - path = DatumGetJsonPathP(jexpr->path_spec->constvalue); + path = DatumGetJsonPathP(op->d.jsonexpr.pathspec->value); /* reset JSON path variable contexts */ foreach(lc, op->d.jsonexpr.args) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8d6c8b868c..8fb1bbb3cf 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2498,7 +2498,7 @@ _copyJsonCommon(const JsonCommon *from) JsonCommon *newnode = makeNode(JsonCommon); COPY_NODE_FIELD(expr); - COPY_STRING_FIELD(pathspec); + COPY_NODE_FIELD(pathspec); COPY_STRING_FIELD(pathname); COPY_NODE_FIELD(passing); COPY_LOCATION_FIELD(location); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 7e0fa5006a..b312c59e44 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3231,6 +3231,7 @@ expression_tree_mutator(Node *node, JsonExpr *newnode; FLATCOPY(newnode, jexpr, JsonExpr); + MUTATE(newnode->raw_expr, jexpr->path_spec, Node *); MUTATE(newnode->raw_expr, jexpr->raw_expr, Node *); MUTATE(newnode->formatted_expr, jexpr->formatted_expr, Node *); MUTATE(newnode->result_coercion, jexpr->result_coercion, JsonCoercion *); @@ -4003,6 +4004,8 @@ raw_expression_tree_walker(Node *node, if (walker(jc->expr, context)) return true; + if (walker(jc->pathspec, context)) + return true; if (walker(jc->passing, context)) return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 64b678c406..29ab36681c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -617,6 +617,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_aggregate_func json_object_aggregate_constructor json_array_aggregate_constructor + json_path_specification %type json_arguments json_passing_clause_opt @@ -626,8 +627,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type json_returning_clause_opt -%type json_path_specification - json_table_path_name +%type json_table_path_name json_as_path_name_clause_opt %type json_encoding @@ -832,6 +832,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %nonassoc UNBOUNDED /* ideally should 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 %nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' @@ -14828,7 +14829,7 @@ json_context_item: ; json_path_specification: - Sconst { $$ = $1; } + a_expr { $$ = $1; } ; json_as_path_name_clause_opt: diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f2959cba9e..c0d06e40e9 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4502,7 +4502,7 @@ static JsonExpr * transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) { JsonExpr *jsexpr = makeNode(JsonExpr); - Datum jsonpath; + Node *pathspec; JsonFormatType format; if (func->common->pathname) @@ -4533,12 +4533,19 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) jsexpr->format = func->common->expr->format; - /* parse JSON path string */ - jsonpath = DirectFunctionCall1(jsonpath_in, - CStringGetDatum(func->common->pathspec)); + pathspec = transformExprRecurse(pstate, func->common->pathspec); - jsexpr->path_spec = makeConst(JSONPATHOID, -1, InvalidOid, -1, - jsonpath, false, false); + 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, diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4c3c26c249..a2ed7385a7 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -469,6 +469,8 @@ static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); +static void get_json_path_spec(Node *path_spec, deparse_context *context, + bool showimplicit); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -7819,6 +7821,19 @@ get_rule_expr_paren(Node *node, deparse_context *context, appendStringInfoChar(context->buf, ')'); } + +/* + * 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 structure */ @@ -9078,7 +9093,7 @@ get_rule_expr(Node *node, deparse_context *context, appendStringInfoString(buf, ", "); - get_const_expr(jexpr->path_spec, context, -1); + get_json_path_spec(jexpr->path_spec, context, showimplicit); if (jexpr->passing.values) { diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 06b6a39d7a..aa862bf6b5 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -672,7 +672,8 @@ typedef struct ExprEvalStep { Datum value; bool isnull; - } *raw_expr; /* raw context item value */ + } *raw_expr, /* raw context item value */ + *pathspec; /* path specification value */ ExprState *formatted_expr; /* formatted context item */ ExprState *result_expr; /* coerced to output type */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 342ce6f1f1..bc894050ca 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1523,7 +1523,7 @@ typedef struct JsonCommon { NodeTag type; JsonValueExpr *expr; /* context item expression */ - JsonPathSpec pathspec; /* JSON path specification */ + 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 */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7383e87607..52d6802c5c 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1342,7 +1342,7 @@ typedef struct JsonExpr Node *formatted_expr; /* formatted context item expression */ JsonCoercion *result_coercion; /* resulting coercion to RETURNING type */ JsonFormat format; /* context item format (JSON/JSONB) */ - Const *path_spec; /* JSON path specification */ + Node *path_spec; /* JSON path specification expression */ JsonPassing passing; /* PASSING clause arguments */ JsonReturning returning; /* RETURNING clause type/format info */ JsonBehavior on_empty; /* ON EMPTY behavior */ diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index f03cdf4cc6..e36a245618 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -869,3 +869,37 @@ 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; +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); + json_value +------------ + foo +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); + json_query +------------ + 123 +(1 row) + +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); + json_query +------------ + [123] +(1 row) + +-- Should fail (invalid path) +SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); +ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index e95d3cf20a..5c223b529a 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -264,3 +264,12 @@ INSERT INTO test_jsonb_constraints VALUES ('{"a": 7}', 1); INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); DROP TABLE test_jsonb_constraints; + +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); +-- Should fail (invalid path) +SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); From d631ce1f1ba6d8bb6afc4212ba2e28b2854c9c3e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 30 Nov 2017 19:02:53 +0300 Subject: [PATCH 33/66] Add subtransactions for JsonExpr execution --- src/backend/executor/execExprInterp.c | 30 ++++++++++++++++- src/backend/optimizer/util/clauses.c | 11 ++++++ src/include/executor/execExpr.h | 1 + src/test/regress/expected/jsonb_sqljson.out | 37 +++++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 16 +++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 30c1eaac8c..76a8e32a10 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -57,6 +57,8 @@ #include "postgres.h" #include "access/tuptoaster.h" +#include "access/xact.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/sequence.h" #include "executor/execExpr.h" @@ -77,6 +79,7 @@ #include "utils/jsonb.h" #include "utils/jsonpath.h" #include "utils/lsyscache.h" +#include "utils/resowner.h" #include "utils/timestamp.h" #include "utils/typcache.h" #include "utils/xml.h" @@ -4501,6 +4504,12 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, return res; } +bool +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) +{ + return jsexpr->on_error.btype != JSON_BEHAVIOR_ERROR; +} + /* ---------------------------------------------------------------- * ExecEvalJson * ---------------------------------------------------------------- @@ -4540,7 +4549,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) var->evaluated = false; } - if (jexpr->on_error.btype == JSON_BEHAVIOR_ERROR) + if (!ExecEvalJsonNeedsSubTransaction(jexpr)) { /* No need to use PG_TRY/PG_CATCH with subtransactions. */ res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, @@ -4548,12 +4557,26 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) } else { + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and + * execute corresponding ON ERROR behavior. + */ MemoryContext oldcontext = CurrentMemoryContext; + ResourceOwner oldowner = CurrentResourceOwner; + + BeginInternalSubTransaction(NULL); + /* Want to execute expressions inside function's memory context */ + MemoryContextSwitchTo(oldcontext); PG_TRY(); { res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, op->resnull); + + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; } PG_CATCH(); { @@ -4564,6 +4587,11 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) edata = CopyErrorData(); FlushErrorState(); + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != ERRCODE_DATA_EXCEPTION) ReThrowError(edata); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 789908249d..ddc9cd10d8 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" @@ -1068,6 +1069,16 @@ 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)) + context->max_hazard = PROPARALLEL_UNSAFE; + return true; + } + /* Recurse to check arguments */ return expression_tree_walker(node, max_parallel_hazard_walker, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index aa862bf6b5..070c3ca913 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -809,6 +809,7 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, JsonReturning *returning, struct JsonCoercionsState *coercions, struct JsonCoercionState **pjcstate); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index e36a245618..ebb4118284 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -903,3 +903,40 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input +-- Test parallel JSON_VALUE() +CREATE TABLE test_parallel_jsonb_value AS +SELECT i::text::jsonb AS js +FROM generate_series(1, 1000000) i; +-- Should be non-parallel due to subtransactions +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + QUERY PLAN +--------------------------------------------- + Aggregate + -> Seq Scan on test_parallel_jsonb_value +(2 rows) + +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + sum +-------------- + 500000500000 +(1 row) + +-- Should be parallel +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + QUERY PLAN +------------------------------------------------------------------ + Finalize Aggregate + -> Gather + Workers Planned: 2 + -> Partial Aggregate + -> Parallel Seq Scan on test_parallel_jsonb_value +(5 rows) + +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + sum +-------------- + 500000500000 +(1 row) + diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 5c223b529a..30f956b4c3 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -273,3 +273,19 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); + +-- Test parallel JSON_VALUE() +CREATE TABLE test_parallel_jsonb_value AS +SELECT i::text::jsonb AS js +FROM generate_series(1, 1000000) i; + +-- Should be non-parallel due to subtransactions +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value; + +-- Should be parallel +EXPLAIN (COSTS OFF) +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; +SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; + From 76d01c69ec872aec2e04528da0f42bb27f4c67a5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 15 Mar 2018 01:46:34 +0300 Subject: [PATCH 34/66] Remove ExecExprPassingCaseValue() --- src/backend/executor/execExpr.c | 126 ++++++++++++++------------ src/backend/executor/execExprInterp.c | 56 +++--------- src/include/executor/execExpr.h | 2 + src/include/executor/executor.h | 2 + 4 files changed, 88 insertions(+), 98 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index caf87c7b08..3d7d521d92 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -82,6 +82,40 @@ static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate, int transno, int setno, int setoff, bool ishash); +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 * @@ -120,32 +154,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); } /* @@ -157,32 +166,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); } /* @@ -2136,11 +2133,19 @@ ExecInitExprRec(Expr *node, ExprState *state, &scratch.d.jsonexpr.pathspec->isnull); scratch.d.jsonexpr.formatted_expr = - ExecInitExpr((Expr *) jexpr->formatted_expr, state->parent); + ExecInitExprWithCaseValue((Expr *) jexpr->formatted_expr, + state->parent, + &scratch.d.jsonexpr.raw_expr->value, + &scratch.d.jsonexpr.raw_expr->isnull); + + scratch.d.jsonexpr.res_expr = + palloc(sizeof(*scratch.d.jsonexpr.res_expr)); scratch.d.jsonexpr.result_expr = jexpr->result_coercion - ? ExecInitExpr((Expr *) jexpr->result_coercion->expr, - state->parent) + ? 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 = @@ -2190,6 +2195,14 @@ ExecInitExprRec(Expr *node, ExprState *state, { 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; @@ -2198,8 +2211,9 @@ ExecInitExprRec(Expr *node, ExprState *state, { cstate->coercion = *coercion; cstate->estate = *coercion ? - ExecInitExpr((Expr *)(*coercion)->expr, - state->parent) : NULL; + ExecInitExprWithCaseValue((Expr *)(*coercion)->expr, + state->parent, + caseval, casenull) : NULL; } } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 76a8e32a10..6c40550c92 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4158,40 +4158,6 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op, tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot); } -/* - * Evaluate a expression substituting specified value in its CaseTestExpr nodes. - */ -static Datum -ExecEvalExprPassingCaseValue(ExprState *estate, ExprContext *econtext, - bool *isnull, - Datum caseval_datum, bool caseval_isnull) -{ - Datum res; - Datum save_datum = econtext->caseValue_datum; - bool save_isNull = econtext->caseValue_isNull; - - econtext->caseValue_datum = caseval_datum; - econtext->caseValue_isNull = caseval_isnull; - - PG_TRY(); - { - res = ExecEvalExpr(estate, econtext, isnull); - } - PG_CATCH(); - { - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - - PG_RE_THROW(); - } - PG_END_TRY(); - - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - - return res; -} - /* * Evaluate a JSON error/empty behavior result. */ @@ -4251,8 +4217,12 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, jexpr->returning.typmod); } else if (op->d.jsonexpr.result_expr) - res = ExecEvalExprPassingCaseValue(op->d.jsonexpr.result_expr, econtext, - isNull, res, *isNull); + { + op->d.jsonexpr.res_expr->value = res; + op->d.jsonexpr.res_expr->isnull = *isNull; + + res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull); + } else if (coercion && coercion->via_populate) res = json_populate_type(res, JSONBOID, jexpr->returning.typid, @@ -4415,8 +4385,10 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, { bool isnull; - item = ExecEvalExprPassingCaseValue(op->d.jsonexpr.formatted_expr, - econtext, &isnull, item, false); + op->d.jsonexpr.raw_expr->value = item; + op->d.jsonexpr.raw_expr->isnull = false; + + item = ExecEvalExpr(op->d.jsonexpr.formatted_expr, econtext, &isnull); if (isnull) { /* execute domain checks for NULLs */ @@ -4463,10 +4435,10 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, } else if (jcstate->estate) { - res = ExecEvalExprPassingCaseValue(jcstate->estate, - econtext, - resnull, - res, false); + op->d.jsonexpr.coercion_expr->value = res; + op->d.jsonexpr.coercion_expr->isnull = false; + + res = ExecEvalExpr(jcstate->estate, econtext, resnull); } /* else no coercion */ } diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 070c3ca913..9f15141ad9 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -673,6 +673,8 @@ typedef struct ExprEvalStep Datum value; bool isnull; } *raw_expr, /* raw context item value */ + *res_expr, /* result item */ + *coercion_expr, /* input for JSON item coercion */ *pathspec; /* path specification value */ ExprState *formatted_expr; /* formatted context item */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index d056fd6151..cf24f55219 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -245,6 +245,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); From 2d13d030692c07e908e15608a1f29c2182d6802b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 03:19:46 +0300 Subject: [PATCH 35/66] Fix jsonpath timestamptz encoding in jsonb tests --- src/test/regress/expected/jsonb_sqljson.out | 50 +++++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 12 +++++ 2 files changed, 62 insertions(+) diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index ebb4118284..d5eae6f387 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -436,6 +436,37 @@ SELECT JSON_VALUE(jsonb 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING poi (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, '$'), @@ -799,6 +830,25 @@ SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null); 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, diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 30f956b4c3..638956d590 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -114,6 +114,13 @@ FROM 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 @@ -229,6 +236,11 @@ SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb 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 ( From d1dc4b5ac09febfad527c981d6ac486f93c5578f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 3 Jul 2018 14:18:33 +0300 Subject: [PATCH 36/66] Fix JSON_VALUE coercion via IO --- src/backend/executor/execExprInterp.c | 57 +++++++++++++-------- src/test/regress/expected/jsonb_sqljson.out | 8 +-- src/test/regress/sql/jsonb_sqljson.sql | 2 +- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 6c40550c92..32c6d660e8 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4412,26 +4412,44 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, op->d.jsonexpr.args); struct JsonCoercionState *jcstate; - if (!jbv) + if (!jbv) /* NULL or empty */ break; + Assert(!empty); + *resnull = false; + /* coerce item datum 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(JsonItemToJsonb(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); - /* coerce item datum to the output type */ - if ((jcstate->coercion && + if (jcstate->coercion && (jcstate->coercion->via_io || - jcstate->coercion->via_populate)) || /* ignored for scalars jsons */ - jexpr->returning.typid == JSONOID || - jexpr->returning.typid == JSONBOID) + jcstate->coercion->via_populate)) { - /* use coercion via I/O from json[b] to the output type */ - res = JsonbPGetDatum(JsonItemToJsonb(jbv)); - res = ExecEvalJsonExprCoercion(op, econtext, res, resnull); + /* + * 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_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE + * but does not define its number. + */ + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg("SQL/JSON item cannot be cast to target type"))); } else if (jcstate->estate) { @@ -4441,17 +4459,16 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, res = ExecEvalExpr(jcstate->estate, econtext, resnull); } /* else no coercion */ + + return res; } - break; case IS_JSON_EXISTS: - res = BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); *resnull = false; - break; + return BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); default: - elog(ERROR, "unrecognized SQL/JSON expression op %d", - jexpr->op); + elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); return (Datum) 0; } @@ -4465,15 +4482,13 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, /* execute ON EMPTY behavior */ res = ExecEvalJsonBehavior(econtext, &jexpr->on_empty, op->d.jsonexpr.default_on_empty, resnull); - } - if (jexpr->op != IS_JSON_EXISTS && - (!empty ? jexpr->op != IS_JSON_VALUE : - /* result is already coerced in DEFAULT behavior case */ - jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT)) - res = ExecEvalJsonExprCoercion(op, econtext, res, resnull); + /* result is already coerced in DEFAULT behavior case */ + if (jexpr->on_empty.btype == JSON_BEHAVIOR_DEFAULT) + return res; + } - return res; + return ExecEvalJsonExprCoercion(op, econtext, res, resnull); } bool diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index d5eae6f387..d4c7b93beb 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -190,12 +190,8 @@ SELECT JSON_VALUE(jsonb '123', '$' RETURNING text); (1 row) /* jsonb bytea ??? */ -SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea); - json_value ------------- - \x313233 -(1 row) - +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 ------------ diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 638956d590..8bb9e016c7 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -46,7 +46,7 @@ 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); +SELECT JSON_VALUE(jsonb '123', '$' RETURNING bytea ERROR ON ERROR); SELECT JSON_VALUE(jsonb '1.23', '$'); SELECT JSON_VALUE(jsonb '1.23', '$' RETURNING int); From a7332c0b617a2c914dd91cdb44f398d86a5d222c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:03:42 +0300 Subject: [PATCH 37/66] Add json support for JSON_EXISTS, JSON_VALUE, JSON_QUERY --- src/backend/executor/execExprInterp.c | 79 +- src/backend/parser/parse_expr.c | 13 - src/backend/utils/adt/jsonpath_exec.c | 41 +- src/include/executor/execExpr.h | 2 +- src/include/utils/jsonpath.h | 14 +- src/test/regress/expected/json_sqljson.out | 1029 +++++++++++++++++++- src/test/regress/sql/json_sqljson.sql | 291 +++++- 7 files changed, 1388 insertions(+), 81 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 32c6d660e8..694983ba52 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4163,17 +4163,21 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op, */ static Datum ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, - ExprState *default_estate, bool *is_null) + ExprState *default_estate, bool is_jsonb, bool *is_null) { *is_null = false; switch (behavior->btype) { case JSON_BEHAVIOR_EMPTY_ARRAY: - return JsonbPGetDatum(JsonbMakeEmptyArray()); + return is_jsonb + ? JsonbPGetDatum(JsonbMakeEmptyArray()) + : PointerGetDatum(cstring_to_text("[]")); case JSON_BEHAVIOR_EMPTY_OBJECT: - return JsonbPGetDatum(JsonbMakeEmptyObject()); + return is_jsonb + ? JsonbPGetDatum(JsonbMakeEmptyObject()) + : PointerGetDatum(cstring_to_text("{}")); case JSON_BEHAVIOR_TRUE: return BoolGetDatum(true); @@ -4200,17 +4204,20 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, */ static Datum ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, - Datum res, bool *isNull) + Datum res, bool *isNull, bool isJsonb) { JsonExpr *jexpr = op->d.jsonexpr.jsexpr; JsonCoercion *coercion = jexpr->result_coercion; - Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res); + Jsonb *jb = *isNull || !isJsonb ? NULL : DatumGetJsonbP(res); + Json *js = *isNull || isJsonb ? NULL : DatumGetJsonP(res); if ((coercion && coercion->via_io) || - (jexpr->omit_quotes && !*isNull && JB_ROOT_IS_SCALAR(jb))) + (jexpr->omit_quotes && !*isNull && + (isJsonb ? JB_ROOT_IS_SCALAR(jb) : JsonContainerIsScalar(&js->root)))) { /* strip quotes and call typinput function */ - char *str = *isNull ? NULL : JsonbUnquote(jb); + char *str = *isNull ? NULL : + (isJsonb ? JsonbUnquote(jb) : JsonUnquote(js)); res = InputFunctionCall(&op->d.jsonexpr.input.func, str, op->d.jsonexpr.input.typioparam, @@ -4224,7 +4231,7 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull); } else if (coercion && coercion->via_populate) - res = json_populate_type(res, JSONBOID, + res = json_populate_type(res, isJsonb ? JSONBOID : JSONOID, jexpr->returning.typid, jexpr->returning.typmod, &op->d.jsonexpr.cache, @@ -4287,7 +4294,7 @@ EvalJsonPathVar(void *cxt, bool isJsonb, char *varName, int varNameLen, * corresponding SQL type and a pointer to the coercion state. */ Datum -ExecPrepareJsonItemCoercion(JsonItem *item, +ExecPrepareJsonItemCoercion(JsonItem *item, bool is_jsonb, JsonReturning *returning, struct JsonCoercionsState *coercions, struct JsonCoercionState **pcoercion) @@ -4297,14 +4304,11 @@ ExecPrepareJsonItemCoercion(JsonItem *item, JsonItem buf; if (JsonItemIsBinary(item) && - JsonContainerIsScalar(JsonItemBinary(item).data)) - { - bool res PG_USED_FOR_ASSERTS_ONLY; - - res = JsonbExtractScalar(JsonItemBinary(item).data, JsonItemJbv(&buf)); + (is_jsonb ? + JsonbExtractScalar(JsonItemBinary(item).data, JsonItemJbv(&buf)) : + JsonExtractScalar((JsonContainer *) JsonItemBinary(item).data, + JsonItemJbv(&buf)))) item = &buf; - Assert(res); - } /* get coercion state reference and datum of the corresponding SQL type */ switch (JsonItemGetType(item)) @@ -4361,7 +4365,7 @@ ExecPrepareJsonItemCoercion(JsonItem *item, case jbvObject: case jbvBinary: coercion = &coercions->composite; - res = JsonbPGetDatum(JsonItemToJsonb(item)); + res = JsonItemToJsonxDatum(item, is_jsonb); break; default: @@ -4376,7 +4380,8 @@ ExecPrepareJsonItemCoercion(JsonItem *item, static Datum ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, - JsonExpr *jexpr, JsonPath *path, Datum item, bool *resnull) + JsonExpr *jexpr, JsonPath *path, Datum item, bool isjsonb, + bool *resnull) { bool empty = false; Datum res = (Datum) 0; @@ -4392,7 +4397,8 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, if (isnull) { /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull); + (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull, + isjsonb); *resnull = true; return (Datum) 0; } @@ -4401,15 +4407,15 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, switch (jexpr->op) { case IS_JSON_QUERY: - res = JsonbPathQuery(item, path, jexpr->wrapper, &empty, - op->d.jsonexpr.args); + res = JsonPathQuery(item, path, jexpr->wrapper, &empty, + op->d.jsonexpr.args, isjsonb); *resnull = !DatumGetPointer(res); break; case IS_JSON_VALUE: { - JsonItem *jbv = JsonbPathValue(item, path, &empty, - op->d.jsonexpr.args); + JsonItem *jbv = JsonPathValue(item, path, &empty, + op->d.jsonexpr.args, isjsonb); struct JsonCoercionState *jcstate; if (!jbv) /* NULL or empty */ @@ -4424,12 +4430,12 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, jexpr->returning.typid == JSONBOID) { /* Use result coercion from json[b] to the output type */ - res = JsonbPGetDatum(JsonItemToJsonb(jbv)); + res = JsonItemToJsonxDatum(jbv, isjsonb); break; } /* Use coercion from SQL/JSON item type to the output type */ - res = ExecPrepareJsonItemCoercion(jbv, + res = ExecPrepareJsonItemCoercion(jbv, isjsonb, &op->d.jsonexpr.jsexpr->returning, &op->d.jsonexpr.coercions, &jcstate); @@ -4465,7 +4471,8 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, case IS_JSON_EXISTS: *resnull = false; - return BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); + return BoolGetDatum(JsonPathExists(item, path, op->d.jsonexpr.args, + isjsonb)); default: elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); @@ -4481,14 +4488,15 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, /* execute ON EMPTY behavior */ res = ExecEvalJsonBehavior(econtext, &jexpr->on_empty, - op->d.jsonexpr.default_on_empty, resnull); + op->d.jsonexpr.default_on_empty, + isjsonb, resnull); /* result is already coerced in DEFAULT behavior case */ if (jexpr->on_empty.btype == JSON_BEHAVIOR_DEFAULT) return res; } - return ExecEvalJsonExprCoercion(op, econtext, res, resnull); + return ExecEvalJsonExprCoercion(op, econtext, res, resnull, isjsonb); } bool @@ -4509,6 +4517,10 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) Datum res = (Datum) 0; JsonPath *path; ListCell *lc; + Oid formattedType = exprType(jexpr->formatted_expr ? + jexpr->formatted_expr : + jexpr->raw_expr); + bool isjsonb = formattedType == JSONBOID; *op->resnull = true; /* until we get a result */ *op->resvalue = (Datum) 0; @@ -4516,7 +4528,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) if (op->d.jsonexpr.raw_expr->isnull || op->d.jsonexpr.pathspec->isnull) { /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, isjsonb); Assert(*op->resnull); *op->resnull = true; @@ -4539,7 +4551,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) if (!ExecEvalJsonNeedsSubTransaction(jexpr)) { /* No need to use PG_TRY/PG_CATCH with subtransactions. */ - res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, isjsonb, op->resnull); } else @@ -4558,7 +4570,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) PG_TRY(); { res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, - op->resnull); + isjsonb, op->resnull); /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); @@ -4585,12 +4597,13 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) /* Execute ON ERROR behavior. */ res = ExecEvalJsonBehavior(econtext, &jexpr->on_error, op->d.jsonexpr.default_on_error, - op->resnull); + isjsonb, op->resnull); if (jexpr->op != IS_JSON_EXISTS && /* result is already coerced in DEFAULT behavior case */ jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT) - res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull); + res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, + isjsonb); } PG_END_TRY(); } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index c0d06e40e9..22937b3281 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4773,13 +4773,10 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) JsonExpr *jsexpr = transformJsonExprCommon(pstate, func); Node *contextItemExpr = jsexpr->formatted_expr ? jsexpr->formatted_expr : jsexpr->raw_expr; - const char *func_name = NULL; switch (func->op) { case IS_JSON_VALUE: - func_name = "JSON_VALUE"; - transformJsonFuncExprOutput(pstate, func, jsexpr); jsexpr->returning.format.type = JS_FORMAT_DEFAULT; @@ -4800,8 +4797,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) break; case IS_JSON_QUERY: - func_name = "JSON_QUERY"; - transformJsonFuncExprOutput(pstate, func, jsexpr); jsexpr->wrapper = func->wrapper; @@ -4810,8 +4805,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) break; case IS_JSON_EXISTS: - func_name = "JSON_EXISTS"; - jsexpr->returning.format.type = JS_FORMAT_DEFAULT; jsexpr->returning.format.encoding = JS_ENC_DEFAULT; jsexpr->returning.format.location = -1; @@ -4821,11 +4814,5 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) 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; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index ce3ee02857..83c0b59e74 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -362,8 +362,6 @@ static JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); static Jsonx *JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb); -static Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb); -static Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb); static bool tryToParseDatetime(text *fmt, text *datetime, char *tzname, bool strict, Datum *value, Oid *typid, @@ -2992,7 +2990,7 @@ JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb) (Jsonx *) JsonbValueToJson(jbv); } -static Datum +Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb) { return isJsonb ? @@ -3000,7 +2998,7 @@ JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb) JsonPGetDatum(JsonbValueToJson(jbv)); } -static Datum +Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb) { JsonbValue jbv; @@ -3335,11 +3333,11 @@ JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) /********************Interface to pgsql's executor***************************/ bool -JsonbPathExists(Datum jb, JsonPath *jp, List *vars) +JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool isJsonb) { + Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar, - (Jsonx *) DatumGetJsonbP(jb), - true, true, NULL); + js, isJsonb, true, NULL); Assert(!jperIsError(res)); @@ -3347,17 +3345,17 @@ JsonbPathExists(Datum jb, JsonPath *jp, List *vars) } Datum -JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, - bool *empty, List *vars) +JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, + List *vars, bool isJsonb) { + Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonItem *first; bool wrap; JsonValueList found = {0}; JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; int count; - jper = executeJsonPath(jp, vars, EvalJsonPathVar, - (Jsonx *) DatumGetJsonbP(jb), true, true, &found); + jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, true, &found); Assert(!jperIsError(jper)); count = JsonValueListLength(&found); @@ -3382,7 +3380,11 @@ JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, } if (wrap) - return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found, true))); + { + JsonbValue *arr = wrapItemsInArray(&found, isJsonb); + + return JsonbValueToJsonxDatum(arr, isJsonb); + } if (count > 1) ereport(ERROR, @@ -3393,22 +3395,22 @@ JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, "sequence into array"))); if (first) - return JsonbPGetDatum(JsonItemToJsonb(first)); + return JsonItemToJsonxDatum(first, isJsonb); *empty = true; return PointerGetDatum(NULL); } JsonItem * -JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) +JsonPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars, bool isJsonb) { + Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonItem *res; JsonValueList found = { 0 }; JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; int count; - jper = executeJsonPath(jp, vars, EvalJsonPathVar, - (Jsonx *) DatumGetJsonbP(jb), true, true, &found); + jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, true, &found); Assert(!jperIsError(jper)); count = JsonValueListLength(&found); @@ -3428,7 +3430,12 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) if (JsonItemIsBinary(res) && JsonContainerIsScalar(JsonItemBinary(res).data)) - JsonbExtractScalar(JsonItemBinary(res).data, JsonItemJbv(res)); + { + if (isJsonb) + JsonbExtractScalar(JsonItemBinary(res).data, JsonItemJbv(res)); + else + JsonExtractScalar((JsonContainer *) JsonItemBinary(res).data, JsonItemJbv(res)); + } if (!JsonItemIsScalar(res)) ereport(ERROR, diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 9f15141ad9..caf5f66ead 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -807,7 +807,7 @@ extern void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot); extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, +extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, bool is_jsonb, JsonReturning *returning, struct JsonCoercionsState *coercions, struct JsonCoercionState **pjcstate); diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index bf8d6539c9..4034434173 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -336,12 +336,14 @@ extern Jsonb *JsonItemToJsonb(JsonItem *jsi); extern Json *JsonItemToJson(JsonItem *jsi); extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonItem *res, bool isJsonb); - -extern bool JsonbPathExists(Datum jb, JsonPath *path, List *vars); -extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, - bool *empty, List *vars); -extern JsonItem *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, - List *vars); +extern Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb); +extern Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb); + +extern bool JsonPathExists(Datum jb, JsonPath *path, List *vars, bool isJsonb); +extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars, bool isJsonb); +extern JsonItem *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, + List *vars, bool isJsonb); extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index bb62634314..8e237c5171 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1,15 +1,1024 @@ -- 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_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::text FORMAT JSON, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::bytea FORMAT JSON, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::json FORMAT JSON, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::jsonb FORMAT JSON, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::json, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSON, '$'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSON, '$' TRUE ON ERROR); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSON, '$' FALSE ON ERROR); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSON, '$' UNKNOWN ON ERROR); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSON, '$' ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT JSON_EXISTS(bytea '' FORMAT JSON, '$' ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT JSON_EXISTS(json '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS('[]' FORMAT JSON, '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSON) FORMAT JSON, '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '1', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json 'null', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '[]', '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '1', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '1', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '1', 'strict $.a' ERROR ON ERROR); +ERROR: jsonpath member accessor can only be applied to an object +SELECT JSON_EXISTS(json 'null', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '[]', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'strict $.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'lax $.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '{}', '$.a'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '{"b": 1, "a": 2}', '$.a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '1', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '{"a": {"b": 1}}', '$.a.b'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.a.b'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + json_exists +------------- + f +(1 row) + +-- extension: boolean expressions +SELECT JSON_EXISTS(json '1', '$ > 2'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(json '1', '$.a > 2' ERROR ON ERROR); + json_exists +------------- + t +(1 row) + -- JSON_VALUE +SELECT JSON_VALUE(NULL, '$'); + json_value +------------ + +(1 row) + 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_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::text, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::bytea, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::json, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::jsonb FORMAT JSON, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_value +------------ + +(1 row) + +SELECT JSON_VALUE('' FORMAT JSON, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE('' FORMAT JSON, '$' NULL ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE('' FORMAT JSON, '$' DEFAULT '"default value"' ON ERROR); + json_value +----------------- + "default value" +(1 row) + +SELECT JSON_VALUE('' FORMAT JSON, '$' ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT JSON_VALUE(json 'null', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json 'null', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json 'true', '$'); + json_value +------------ + true +(1 row) + +SELECT JSON_VALUE(json 'true', '$' RETURNING bool); + json_value +------------ + t +(1 row) + +SELECT JSON_VALUE(json '123', '$'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(json '123', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(json '123', '$' RETURNING text); + json_value +------------ + 123 +(1 row) + +/* jsonb bytea ??? */ +SELECT JSON_VALUE(json '123', '$' RETURNING bytea ERROR ON ERROR); +ERROR: SQL/JSON item cannot be cast to target type +SELECT JSON_VALUE(json '1.23', '$'); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(json '1.23', '$' RETURNING int); + json_value +------------ + 1 +(1 row) + +SELECT JSON_VALUE(json '"1.23"', '$' RETURNING numeric); + json_value +------------ + 1.23 +(1 row) + +SELECT JSON_VALUE(json '"1.23"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "1.23" +SELECT JSON_VALUE(json '"aaa"', '$'); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING text); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(5)); + json_value +------------ + aaa +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(2)); + json_value +------------ + aa +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); + json_value +------------ + "aaa" +(1 row) + +SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING json); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING jsonb); + json_value +------------ + "\"aaa\"" +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: "aaa" +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); + json_value +------------ + 111 +(1 row) + +SELECT JSON_VALUE(json '"123"', '$' RETURNING int) + 234; + ?column? +---------- + 357 +(1 row) + +SELECT JSON_VALUE(json '"2017-02-20"', '$' RETURNING date) + 9; + ?column? +------------ + 03-01-2017 +(1 row) + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljson_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null); +ERROR: domain sqljson_int_not_null does not allow null values +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null NULL ON ERROR); +ERROR: domain sqljson_int_not_null does not allow null values +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null DEFAULT NULL ON ERROR); +ERROR: domain sqljson_int_not_null does not allow null values +SELECT JSON_VALUE(json '[]', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '[]', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(json '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '{}', '$' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(json '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'strict $.a' ERROR ON ERROR); +ERROR: jsonpath member accessor can only be applied to an object +SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 'error' ON ERROR); + json_value +------------ + error +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 2 ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT 2 ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 2 +(1 row) + +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + json_value +------------ + 3 +(1 row) + +SELECT JSON_VALUE(json '[1,2]', '$[*]' ERROR ON ERROR); +ERROR: JSON path expression in JSON_VALUE should return singleton scalar item +SELECT JSON_VALUE(json '[1,2]', '$[*]' DEFAULT '0' ON ERROR); + json_value +------------ + 0 +(1 row) + +SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for type integer: " " +SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 5 +(1 row) + +SELECT JSON_VALUE(json '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + json_value +------------ + 1 +(1 row) + +SELECT + x, + JSON_VALUE( + json '{"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(json 'null', '$a' PASSING point ' (1, 2 )' AS a); + json_value +------------ + (1,2) +(1 row) + +SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); + json_value +------------ + (1,2) +(1 row) + -- 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, '$'); - ^ +SELECT + JSON_QUERY(js FORMAT JSON, '$'), + JSON_QUERY(js FORMAT JSON, '$' WITHOUT WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + ('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 FORMAT JSON, 'strict $[*]') AS "unspec", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + ('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('"aaa"' FORMAT JSON, '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES ON SCALAR STRING); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES ON SCALAR STRING); + json_query +------------ + aaa +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT 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('"aaa"' FORMAT JSON, '$' 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('"aaa"' FORMAT JSON, '$' 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(json '[1]', '$' WITH WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER OMIT QUOTES); + ^ +SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES); + ^ +SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...ON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTE... + ^ +SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +ERROR: SQL/JSON QUOTES behavior shall not be specified when WITH WRAPPER is used +LINE 1: ...ON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTE... + ^ +-- Should succeed +SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + json_query +------------ + [1] +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]'); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' NULL ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY ARRAY ON EMPTY); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY OBJECT ON EMPTY); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY NULL ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); + json_query +------------ + [] +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY ERROR ON ERROR); +ERROR: no SQL/JSON item +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON ERROR); + json_query +------------ + +(1 row) + +SELECT JSON_QUERY('[1,2]' FORMAT JSON, '$[*]' 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(json '[1,2]', '$' RETURNING json); + json_query +------------ + [1,2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json FORMAT JSON); + json_query +------------ + [1,2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb FORMAT JSON); + json_query +------------ + [1, 2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text); + json_query +------------ + [1,2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(10)); + json_query +------------ + [1,2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(3)); + json_query +------------ + [1, +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON); + json_query +------------ + [1,2] +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea); + json_query +-------------- + \x5b312c325d +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON); + json_query +-------------- + \x5b312c325d +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); + json_query +------------ + \x7b7d +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + json_query +------------ + {} +(1 row) + +SELECT + x, y, + JSON_QUERY( + json '[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 sqljson_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljson_reca AS (reca sqljson_rec[]); +SELECT JSON_QUERY(json '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljson_rec); + json_query +----------------------------------------------------- + (1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",) +(1 row) + +SELECT * FROM unnest((JSON_QUERY(json '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljson_rec)).jsa); + unnest +------------------------ + {"a": 1, "b": ["foo"]} + {"a": 2, "c": {}} + 123 +(3 rows) + +SELECT * FROM unnest((JSON_QUERY(json '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljson_reca)).reca); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: array types returning +SELECT JSON_QUERY(json '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); + json_query +-------------- + {1,2,NULL,3} +(1 row) + +SELECT * FROM unnest(JSON_QUERY(json '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljson_rec[])); + a | t | js | jb | jsa +---+-------------+----+------------+----- + 1 | ["foo", []] | | | + 2 | | | [{}, true] | +(2 rows) + +-- Extension: domain types returning +SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null); + json_query +------------ + 1 +(1 row) + +SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null); +ERROR: domain sqljson_int_not_null does not allow null values +-- Test constraints +CREATE TABLE test_json_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(json '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_json_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_json_constraint2 + CHECK (JSON_EXISTS(js FORMAT JSON, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_json_constraint3 + CHECK (JSON_VALUE(js::json, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_json_constraint4 + CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_json_constraint5 + CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') +); +\d test_json_constraints + Table "public.test_json_constraints" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------------------------------------------------------------------------------------------------------- + js | text | | | + i | integer | | | + x | jsonb | | | JSON_QUERY('[1,2]'::json, '$[*]' RETURNING json WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) +Check constraints: + "test_json_constraint1" CHECK (js IS JSON) + "test_json_constraint2" CHECK (JSON_EXISTS(js FORMAT JSON, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) + "test_json_constraint3" CHECK (JSON_VALUE(js::json, '$."a"' RETURNING integer DEFAULT ('12'::text || i)::integer ON EMPTY ERROR ON ERROR) > i) + "test_json_constraint4" CHECK (JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_json_constraint5" CHECK (JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar) + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_json_constraint%'; + check_clause +-------------------------------------------------------------------------------------------------------------------------------------- + ((js IS JSON)) + (JSON_EXISTS(js FORMAT JSON, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) + ((JSON_VALUE((js)::json, '$."a"' RETURNING integer DEFAULT (('12'::text || i))::integer ON EMPTY ERROR ON ERROR) > i)) + ((JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_QUERY(js FORMAT JSON, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)) +(5 rows) + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_json_constraints'::regclass; + pg_get_expr +--------------------------------------------------------------------------------------------------------- + JSON_QUERY('[1,2]'::json, '$[*]' RETURNING json WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) +(1 row) + +INSERT INTO test_json_constraints VALUES ('', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint1" +DETAIL: Failing row contains (, 1, [1, 2]). +INSERT INTO test_json_constraints VALUES ('1', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2" +DETAIL: Failing row contains (1, 1, [1, 2]). +INSERT INTO test_json_constraints VALUES ('[]'); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2" +DETAIL: Failing row contains ([], null, [1, 2]). +INSERT INTO test_json_constraints VALUES ('{"b": 1}', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint2" +DETAIL: Failing row contains ({"b": 1}, 1, [1, 2]). +INSERT INTO test_json_constraints VALUES ('{"a": 1}', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint3" +DETAIL: Failing row contains ({"a": 1}, 1, [1, 2]). +INSERT INTO test_json_constraints VALUES ('{"a": 7}', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint5" +DETAIL: Failing row contains ({"a": 7}, 1, [1, 2]). +INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1); +ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint4" +DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]). +DROP TABLE test_json_constraints; +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); + json_exists +------------- + t +(1 row) + +SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'a'); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); + json_value +------------ + foo +(1 row) + +SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a'); + json_query +------------ + 123 +(1 row) + +SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); + json_query +------------ + [123] +(1 row) + +-- Should fail (invalid path) +SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); +ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 4f30fa46b9..823be2cc8e 100644 --- a/src/test/regress/sql/json_sqljson.sql +++ b/src/test/regress/sql/json_sqljson.sql @@ -1,11 +1,300 @@ -- JSON_EXISTS SELECT JSON_EXISTS(NULL FORMAT JSON, '$'); +SELECT JSON_EXISTS(NULL::text FORMAT JSON, '$'); +SELECT JSON_EXISTS(NULL::bytea FORMAT JSON, '$'); +SELECT JSON_EXISTS(NULL::json FORMAT JSON, '$'); +SELECT JSON_EXISTS(NULL::jsonb FORMAT JSON, '$'); +SELECT JSON_EXISTS(NULL::json, '$'); + +SELECT JSON_EXISTS('' FORMAT JSON, '$'); +SELECT JSON_EXISTS('' FORMAT JSON, '$' TRUE ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSON, '$' FALSE ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSON, '$' UNKNOWN ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSON, '$' ERROR ON ERROR); + + +SELECT JSON_EXISTS(bytea '' FORMAT JSON, '$' ERROR ON ERROR); + +SELECT JSON_EXISTS(json '[]', '$'); +SELECT JSON_EXISTS('[]' FORMAT JSON, '$'); +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSON) FORMAT JSON, '$'); + +SELECT JSON_EXISTS(json '1', '$'); +SELECT JSON_EXISTS(json 'null', '$'); +SELECT JSON_EXISTS(json '[]', '$'); + +SELECT JSON_EXISTS(json '1', '$.a'); +SELECT JSON_EXISTS(json '1', 'strict $.a'); +SELECT JSON_EXISTS(json '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_EXISTS(json 'null', '$.a'); +SELECT JSON_EXISTS(json '[]', '$.a'); +SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'strict $.a'); +SELECT JSON_EXISTS(json '[1, "aaa", {"a": 1}]', 'lax $.a'); +SELECT JSON_EXISTS(json '{}', '$.a'); +SELECT JSON_EXISTS(json '{"b": 1, "a": 2}', '$.a'); + +SELECT JSON_EXISTS(json '1', '$.a.b'); +SELECT JSON_EXISTS(json '{"a": {"b": 1}}', '$.a.b'); +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.a.b'); + +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING 1 AS x); +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x)' PASSING '1' AS x); +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 2 AS y); +SELECT JSON_EXISTS(json '{"a": 1, "b": 2}', '$.* ? (@ > $x && @ < $y)' PASSING 0 AS x, 1 AS y); + +-- extension: boolean expressions +SELECT JSON_EXISTS(json '1', '$ > 2'); +SELECT JSON_EXISTS(json '1', '$.a > 2' ERROR ON ERROR); -- JSON_VALUE +SELECT JSON_VALUE(NULL, '$'); SELECT JSON_VALUE(NULL FORMAT JSON, '$'); +SELECT JSON_VALUE(NULL::text, '$'); +SELECT JSON_VALUE(NULL::bytea, '$'); +SELECT JSON_VALUE(NULL::json, '$'); +SELECT JSON_VALUE(NULL::jsonb FORMAT JSON, '$'); + +SELECT JSON_VALUE('' FORMAT JSON, '$'); +SELECT JSON_VALUE('' FORMAT JSON, '$' NULL ON ERROR); +SELECT JSON_VALUE('' FORMAT JSON, '$' DEFAULT '"default value"' ON ERROR); +SELECT JSON_VALUE('' FORMAT JSON, '$' ERROR ON ERROR); + +SELECT JSON_VALUE(json 'null', '$'); +SELECT JSON_VALUE(json 'null', '$' RETURNING int); + +SELECT JSON_VALUE(json 'true', '$'); +SELECT JSON_VALUE(json 'true', '$' RETURNING bool); + +SELECT JSON_VALUE(json '123', '$'); +SELECT JSON_VALUE(json '123', '$' RETURNING int) + 234; +SELECT JSON_VALUE(json '123', '$' RETURNING text); +/* jsonb bytea ??? */ +SELECT JSON_VALUE(json '123', '$' RETURNING bytea ERROR ON ERROR); + +SELECT JSON_VALUE(json '1.23', '$'); +SELECT JSON_VALUE(json '1.23', '$' RETURNING int); +SELECT JSON_VALUE(json '"1.23"', '$' RETURNING numeric); +SELECT JSON_VALUE(json '"1.23"', '$' RETURNING int ERROR ON ERROR); + +SELECT JSON_VALUE(json '"aaa"', '$'); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING text); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(5)); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING char(2)); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING json ERROR ON ERROR); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING jsonb ERROR ON ERROR); +SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING json); +SELECT JSON_VALUE(json '"\"aaa\""', '$' RETURNING jsonb); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(json '"aaa"', '$' RETURNING int DEFAULT 111 ON ERROR); +SELECT JSON_VALUE(json '"123"', '$' RETURNING int) + 234; + +SELECT JSON_VALUE(json '"2017-02-20"', '$' RETURNING date) + 9; + +-- Test NULL checks execution in domain types +CREATE DOMAIN sqljson_int_not_null AS int NOT NULL; +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null); +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null NULL ON ERROR); +SELECT JSON_VALUE(json '1', '$.a' RETURNING sqljson_int_not_null DEFAULT NULL ON ERROR); + +SELECT JSON_VALUE(json '[]', '$'); +SELECT JSON_VALUE(json '[]', '$' ERROR ON ERROR); +SELECT JSON_VALUE(json '{}', '$'); +SELECT JSON_VALUE(json '{}', '$' ERROR ON ERROR); + +SELECT JSON_VALUE(json '1', '$.a'); +SELECT JSON_VALUE(json '1', 'strict $.a' ERROR ON ERROR); +SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 'error' ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_VALUE(json '1', 'strict $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT 2 ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' NULL ON EMPTY DEFAULT '2' ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' DEFAULT '2' ON EMPTY DEFAULT '3' ON ERROR); +SELECT JSON_VALUE(json '1', 'lax $.a' ERROR ON EMPTY DEFAULT '3' ON ERROR); + +SELECT JSON_VALUE(json '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_VALUE(json '[1,2]', '$[*]' DEFAULT '0' ON ERROR); +SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int ERROR ON ERROR); +SELECT JSON_VALUE(json '[" "]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); +SELECT JSON_VALUE(json '["1"]', '$[*]' RETURNING int DEFAULT 2 + 3 ON ERROR); + +SELECT + x, + JSON_VALUE( + json '{"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(json 'null', '$a' PASSING point ' (1, 2 )' AS a); +SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); -- JSON_QUERY -SELECT JSON_QUERY(NULL FORMAT JSON, '$'); +SELECT + JSON_QUERY(js FORMAT JSON, '$'), + JSON_QUERY(js FORMAT JSON, '$' WITHOUT WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js FORMAT JSON, '$' WITH ARRAY WRAPPER) +FROM + (VALUES + ('null'), + ('12.3'), + ('true'), + ('"aaa"'), + ('[1, null, "2"]'), + ('{"a": 1, "b": [2]}') + ) foo(js); + +SELECT + JSON_QUERY(js FORMAT JSON, 'strict $[*]') AS "unspec", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js FORMAT JSON, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" +FROM + (VALUES + ('1'), + ('[]'), + ('[null]'), + ('[12.3]'), + ('[true]'), + ('["aaa"]'), + ('[[1, 2, 3]]'), + ('[{"a": 1, "b": [2]}]'), + ('[1, "2", null, [3]]') + ) foo(js); + +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + +-- QUOTES behavior should not be specified when WITH WRAPPER used: +-- Should fail +SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(json '[1]', '$' WITH WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER KEEP QUOTES); +SELECT JSON_QUERY(json '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); +-- Should succeed +SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER OMIT QUOTES); +SELECT JSON_QUERY(json '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]'); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY ARRAY ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY); + +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY NULL ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSON, '$[*]' ERROR ON ERROR); + +SELECT JSON_QUERY('[1,2]' FORMAT JSON, '$[*]' ERROR ON ERROR); + +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING json FORMAT JSON); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING jsonb FORMAT JSON); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(10)); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(3)); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON); + +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); + +SELECT + x, y, + JSON_QUERY( + json '[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 sqljson_rec AS (a int, t text, js json, jb jsonb, jsa json[]); +CREATE TYPE sqljson_reca AS (reca sqljson_rec[]); + +SELECT JSON_QUERY(json '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljson_rec); +SELECT * FROM unnest((JSON_QUERY(json '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljson_rec)).jsa); +SELECT * FROM unnest((JSON_QUERY(json '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljson_reca)).reca); + +-- Extension: array types returning +SELECT JSON_QUERY(json '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER); +SELECT * FROM unnest(JSON_QUERY(json '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljson_rec[])); + +-- Extension: domain types returning +SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null); +SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null); + +-- Test constraints + +CREATE TABLE test_json_constraints ( + js text, + i int, + x jsonb DEFAULT JSON_QUERY(json '[1,2]', '$[*]' WITH WRAPPER) + CONSTRAINT test_json_constraint1 + CHECK (js IS JSON) + CONSTRAINT test_json_constraint2 + CHECK (JSON_EXISTS(js FORMAT JSON, '$.a' PASSING i + 5 AS int, i::text AS txt, array[1,2,3] as arr)) + CONSTRAINT test_json_constraint3 + CHECK (JSON_VALUE(js::json, '$.a' RETURNING int DEFAULT ('12' || i)::int ON EMPTY ERROR ON ERROR) > i) + CONSTRAINT test_json_constraint4 + CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING jsonb WITH CONDITIONAL WRAPPER EMPTY OBJECT ON ERROR) < jsonb '[10]') + CONSTRAINT test_json_constraint5 + CHECK (JSON_QUERY(js FORMAT JSON, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') +); + +\d test_json_constraints + +SELECT check_clause +FROM information_schema.check_constraints +WHERE constraint_name LIKE 'test_json_constraint%'; + +SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_json_constraints'::regclass; + +INSERT INTO test_json_constraints VALUES ('', 1); +INSERT INTO test_json_constraints VALUES ('1', 1); +INSERT INTO test_json_constraints VALUES ('[]'); +INSERT INTO test_json_constraints VALUES ('{"b": 1}', 1); +INSERT INTO test_json_constraints VALUES ('{"a": 1}', 1); +INSERT INTO test_json_constraints VALUES ('{"a": 7}', 1); +INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1); + +DROP TABLE test_json_constraints; + +-- Extension: non-constant JSON path +SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY); +SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a'); +SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); +-- Should fail (invalid path) +SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); From aeaf910303389800003fcd32c7c260d957e977f5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 02:45:51 +0300 Subject: [PATCH 38/66] Fix jsonpath timestamptz encoding in json tests --- src/test/regress/expected/json_sqljson.out | 50 ++++++++++++++++++++++ src/test/regress/sql/json_sqljson.sql | 12 ++++++ 2 files changed, 62 insertions(+) diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index 8e237c5171..9fc56b0ff4 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -555,6 +555,37 @@ SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING poin (1,2) (1 row) +-- Test timestamptz passing and output +SELECT JSON_VALUE(json '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(json '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(json '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(json '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(json '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 FORMAT JSON, '$'), @@ -918,6 +949,25 @@ SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null); SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null); ERROR: domain sqljson_int_not_null does not allow null values +-- Test timestamptz passing and output +SELECT JSON_QUERY(json '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(json '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(json '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_json_constraints ( js text, diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 823be2cc8e..3a39f600cb 100644 --- a/src/test/regress/sql/json_sqljson.sql +++ b/src/test/regress/sql/json_sqljson.sql @@ -139,6 +139,13 @@ FROM SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a); SELECT JSON_VALUE(json 'null', '$a' PASSING point ' (1, 2 )' AS a RETURNING point); +-- Test timestamptz passing and output +SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamptz); +SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING timestamp); +SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_VALUE(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + -- JSON_QUERY SELECT @@ -254,6 +261,11 @@ SELECT * FROM unnest(JSON_QUERY(json '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb" SELECT JSON_QUERY(json '{"a": 1}', '$.a' RETURNING sqljson_int_not_null); SELECT JSON_QUERY(json '{"a": 1}', '$.b' RETURNING sqljson_int_not_null); +-- Test timestamptz passing and output +SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts); +SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING json); +SELECT JSON_QUERY(json 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 +10' AS ts RETURNING jsonb); + -- Test constraints CREATE TABLE test_json_constraints ( From fc598eab5d1e632b41df873486f83d6f44abec96 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sun, 3 Feb 2019 13:36:29 +0300 Subject: [PATCH 39/66] WIP: optimize SQL/JSON subtransactions --- src/backend/executor/execExprInterp.c | 324 +++++++++++++++++--------- src/backend/optimizer/util/clauses.c | 2 +- src/backend/parser/parse_expr.c | 15 +- src/backend/utils/adt/jsonpath_exec.c | 66 +++++- src/include/executor/execExpr.h | 3 +- src/include/utils/jsonpath.h | 7 +- 6 files changed, 289 insertions(+), 128 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 694983ba52..f638fef524 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4204,42 +4204,55 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, */ static Datum ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext, - Datum res, bool *isNull, bool isJsonb) + Datum res, bool *isNull, bool isJsonb, + void *p, bool *error) { - JsonExpr *jexpr = op->d.jsonexpr.jsexpr; - JsonCoercion *coercion = jexpr->result_coercion; - Jsonb *jb = *isNull || !isJsonb ? NULL : DatumGetJsonbP(res); - Json *js = *isNull || isJsonb ? NULL : DatumGetJsonP(res); + ExprState *estate = p; - if ((coercion && coercion->via_io) || - (jexpr->omit_quotes && !*isNull && - (isJsonb ? JB_ROOT_IS_SCALAR(jb) : JsonContainerIsScalar(&js->root)))) + if (estate) { - /* strip quotes and call typinput function */ - char *str = *isNull ? NULL : - (isJsonb ? JsonbUnquote(jb) : JsonUnquote(js)); + op->d.jsonexpr.coercion_expr->value = res; + op->d.jsonexpr.coercion_expr->isnull = *isNull; - res = InputFunctionCall(&op->d.jsonexpr.input.func, str, - op->d.jsonexpr.input.typioparam, - jexpr->returning.typmod); + return ExecEvalExpr(estate, econtext, isNull); } - else if (op->d.jsonexpr.result_expr) + else { - op->d.jsonexpr.res_expr->value = res; - op->d.jsonexpr.res_expr->isnull = *isNull; + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + JsonCoercion *coercion = jexpr->result_coercion; + Jsonb *jb = *isNull || !isJsonb ? NULL : DatumGetJsonbP(res); + Json *js = *isNull || isJsonb ? NULL : DatumGetJsonP(res); - res = ExecEvalExpr(op->d.jsonexpr.result_expr, econtext, isNull); - } - else if (coercion && coercion->via_populate) - res = json_populate_type(res, isJsonb ? JSONBOID : JSONOID, - jexpr->returning.typid, - jexpr->returning.typmod, - &op->d.jsonexpr.cache, - econtext->ecxt_per_query_memory, - isNull); - /* else no coercion, simply return item */ + if ((coercion && coercion->via_io) || + (jexpr->omit_quotes && !*isNull && + (isJsonb ? JB_ROOT_IS_SCALAR(jb) : JsonContainerIsScalar(&js->root)))) + { + /* strip quotes and call typinput function */ + char *str = *isNull ? NULL : + (isJsonb ? JsonbUnquote(jb) : JsonUnquote(js)); - return res; + res = InputFunctionCall(&op->d.jsonexpr.input.func, str, + op->d.jsonexpr.input.typioparam, + jexpr->returning.typmod); + } + else 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); + } + else if (coercion && coercion->via_populate) + res = json_populate_type(res, isJsonb ? JSONBOID : JSONOID, + jexpr->returning.typid, + jexpr->returning.typmod, + &op->d.jsonexpr.cache, + econtext->ecxt_per_query_memory, + isNull); + /* else no coercion, simply return item */ + + return res; + } } /* @@ -4378,11 +4391,92 @@ ExecPrepareJsonItemCoercion(JsonItem *item, bool is_jsonb, return res; } +typedef Datum (*JsonFunc)(ExprEvalStep *op, ExprContext *econtext, + Datum item, bool *resnull, bool isjsonb, + void *p, bool *error); + static Datum -ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, - JsonExpr *jexpr, JsonPath *path, Datum item, bool isjsonb, - bool *resnull) +ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op, + ExprContext *econtext, + Datum res, bool *resnull, bool isjsonb, + void *p, bool *error, bool subtrans) { + if (subtrans) + { + /* + * We should catch exceptions of category ERRCODE_DATA_EXCEPTION + * and execute the corresponding ON ERROR behavior then. + */ + MemoryContext oldcontext = CurrentMemoryContext; + ResourceOwner 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, isjsonb, 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; + } + else + { + /* No need to use subtransactions. */ + //Assert(!error); + return func(op, econtext, res, resnull, isjsonb, p, error); + } +} + + +typedef struct +{ + JsonPath *path; + bool *error; + bool coercionInSubtrans; +} ExecEvalJsonExprContext; + +static Datum +ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, + Datum item, bool *resnull, bool isjsonb, void *pcxt, + bool *error) +{ + ExecEvalJsonExprContext *cxt = pcxt; + //bool *error = cxt->error; + JsonPath *path = cxt->path; + JsonExpr *jexpr = op->d.jsonexpr.jsexpr; + ExprState *estate = NULL; + bool throwErrors = !!error;//expr->on_error.btype == JSON_BEHAVIOR_ERROR; bool empty = false; Datum res = (Datum) 0; @@ -4390,6 +4484,8 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, { bool isnull; + Assert(!cxt->coercionInSubtrans); + op->d.jsonexpr.raw_expr->value = item; op->d.jsonexpr.raw_expr->isnull = false; @@ -4398,7 +4494,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, { /* execute domain checks for NULLs */ (void) ExecEvalJsonExprCoercion(op, econtext, res, resnull, - isjsonb); + isjsonb, NULL, NULL); *resnull = true; return (Datum) 0; } @@ -4407,16 +4503,22 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, switch (jexpr->op) { case IS_JSON_QUERY: - res = JsonPathQuery(item, path, jexpr->wrapper, &empty, + res = JsonPathQuery(item, path, jexpr->wrapper, + &empty, error, op->d.jsonexpr.args, isjsonb); *resnull = !DatumGetPointer(res); + if (error && *error) + return (Datum) 0; break; case IS_JSON_VALUE: { - JsonItem *jbv = JsonPathValue(item, path, &empty, - op->d.jsonexpr.args, isjsonb); struct JsonCoercionState *jcstate; + JsonItem *jbv = JsonPathValue(item, path, &empty, error, + op->d.jsonexpr.args, isjsonb); + + if (error && *error) + return (Datum) 0; if (!jbv) /* NULL or empty */ break; @@ -4425,7 +4527,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, *resnull = false; - /* coerce item datum to the output type */ + /* coerce scalar item to the output type */ if (jexpr->returning.typid == JSONOID || jexpr->returning.typid == JSONBOID) { @@ -4444,6 +4546,11 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, (jcstate->coercion->via_io || jcstate->coercion->via_populate)) { + if (throwErrors) + { + *error = true; + return (Datum) 0; + } /* * Coercion via I/O means here that the cast to the target * type simply does not exist. @@ -4459,10 +4566,8 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, } else if (jcstate->estate) { - op->d.jsonexpr.coercion_expr->value = res; - op->d.jsonexpr.coercion_expr->isnull = false; - - res = ExecEvalExpr(jcstate->estate, econtext, resnull); + estate = jcstate->estate; /* coerce using expression */ + break; } /* else no coercion */ @@ -4470,9 +4575,14 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, } case IS_JSON_EXISTS: - *resnull = false; - return BoolGetDatum(JsonPathExists(item, path, op->d.jsonexpr.args, - isjsonb)); + { + bool res = JsonPathExists(item, path, + op->d.jsonexpr.args, + isjsonb, error); + + *resnull = error && *error; + return BoolGetDatum(res); + } default: elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); @@ -4482,27 +4592,53 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, if (empty) { if (jexpr->on_empty.btype == JSON_BEHAVIOR_ERROR) + { + if (throwErrors) + { + *error = true; + return (Datum) 0; + } + ereport(ERROR, (errcode(ERRCODE_NO_JSON_ITEM), errmsg("no SQL/JSON item"))); + } - /* execute ON EMPTY behavior */ - res = ExecEvalJsonBehavior(econtext, &jexpr->on_empty, - op->d.jsonexpr.default_on_empty, - isjsonb, resnull); - - /* result is already coerced in DEFAULT behavior case */ if (jexpr->on_empty.btype == JSON_BEHAVIOR_DEFAULT) - return res; + /* + * 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, + isjsonb, resnull); } - return ExecEvalJsonExprCoercion(op, econtext, res, resnull, isjsonb); + return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext, + res, resnull, isjsonb, estate, error, + cxt->coercionInSubtrans); } bool -ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, + struct JsonCoercionsState *coercions) { - return jsexpr->on_error.btype != JSON_BEHAVIOR_ERROR; + if (jsexpr->on_error.btype == JSON_BEHAVIOR_ERROR) + return false; + + if (jsexpr->formatted_expr) + return true; + + if (jsexpr->op == IS_JSON_EXISTS) + return false; + + if (!coercions) + return true; + + return false; } /* ---------------------------------------------------------------- @@ -4512,6 +4648,7 @@ ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) void ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { + ExecEvalJsonExprContext cxt; JsonExpr *jexpr = op->d.jsonexpr.jsexpr; Datum item; Datum res = (Datum) 0; @@ -4521,6 +4658,10 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) jexpr->formatted_expr : jexpr->raw_expr); bool isjsonb = formattedType == JSONBOID; + 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; @@ -4528,7 +4669,8 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) if (op->d.jsonexpr.raw_expr->isnull || op->d.jsonexpr.pathspec->isnull) { /* execute domain checks for NULLs */ - (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, isjsonb); + (void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, + isjsonb, NULL, NULL); Assert(*op->resnull); *op->resnull = true; @@ -4548,64 +4690,30 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) var->evaluated = false; } - if (!ExecEvalJsonNeedsSubTransaction(jexpr)) - { - /* No need to use PG_TRY/PG_CATCH with subtransactions. */ - res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, isjsonb, - op->resnull); - } - else - { - /* - * We should catch exceptions of category ERRCODE_DATA_EXCEPTION and - * execute corresponding ON ERROR behavior. - */ - MemoryContext oldcontext = CurrentMemoryContext; - ResourceOwner oldowner = CurrentResourceOwner; - - BeginInternalSubTransaction(NULL); - /* Want to execute expressions inside function's memory context */ - MemoryContextSwitchTo(oldcontext); - - PG_TRY(); - { - res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, - isjsonb, op->resnull); - - /* 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(); + needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &op->d.jsonexpr.coercions); - /* Abort the inner transaction */ - RollbackAndReleaseCurrentSubTransaction(); - MemoryContextSwitchTo(oldcontext); - CurrentResourceOwner = oldowner; + cxt.path = path; + cxt.error = throwErrors ? NULL : &error; + cxt.coercionInSubtrans = !needSubtrans && !throwErrors; + Assert(!needSubtrans || cxt.error); - if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != ERRCODE_DATA_EXCEPTION) - ReThrowError(edata); + res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item, + op->resnull, isjsonb, &cxt, cxt.error, + needSubtrans); - /* Execute ON ERROR behavior. */ - res = ExecEvalJsonBehavior(econtext, &jexpr->on_error, - op->d.jsonexpr.default_on_error, - isjsonb, op->resnull); + if (error) + { + /* Execute ON ERROR behavior */ + res = ExecEvalJsonBehavior(econtext, &jexpr->on_error, + op->d.jsonexpr.default_on_error, + isjsonb, op->resnull); - if (jexpr->op != IS_JSON_EXISTS && - /* result is already coerced in DEFAULT behavior case */ - jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT) - res = ExecEvalJsonExprCoercion(op, econtext, res, op->resnull, - isjsonb); - } - PG_END_TRY(); + if (jexpr->op != IS_JSON_EXISTS && + /* result is already coerced in DEFAULT behavior case */ + jexpr->on_error.btype != JSON_BEHAVIOR_DEFAULT) + res = ExecEvalJsonExprCoercion(op, econtext, res, + op->resnull, isjsonb, + NULL, NULL); } *op->resvalue = res; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index ddc9cd10d8..6d26f1740c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1074,7 +1074,7 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) { JsonExpr *jsexpr = (JsonExpr *) node; - if (ExecEvalJsonNeedsSubTransaction(jsexpr)) + if (ExecEvalJsonNeedsSubTransaction(jsexpr, NULL)) context->max_hazard = PROPARALLEL_UNSAFE; return true; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 22937b3281..a46edc358c 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -3693,9 +3693,10 @@ makeCaseTestExpr(Node *expr) static Node * transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, JsonFormatType default_format, bool isarg, - Node **rawexpr) + Node **prawexpr) { Node *expr = transformExprRecurse(pstate, (Node *) ve->expr); + Node *rawexpr; JsonFormatType format; Oid exprtype; int location; @@ -3710,13 +3711,15 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, get_type_category_preferred(exprtype, &typcategory, &typispreferred); - if (rawexpr) + rawexpr = expr; + + if (prawexpr) { /* * Save a raw context item expression if it is needed for the isolation * of error handling in the formatting stage. */ - *rawexpr = expr; + *prawexpr = expr; assign_expr_collations(pstate, expr); expr = makeCaseTestExpr(expr); } @@ -3776,6 +3779,7 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, if (format != JS_FORMAT_DEFAULT) { Oid targettype = format == JS_FORMAT_JSONB ? JSONBOID : JSONOID; + Node *orig = expr; Node *coerced; FuncExpr *fexpr; @@ -3803,10 +3807,9 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, location); if (coerced) - expr = coerced; + expr = coerced != orig ? coerced : rawexpr; else { - /* If coercion failed, use to_json()/to_jsonb() functions. */ fexpr = makeFuncExpr(targettype == JSONOID ? F_TO_JSON : F_TO_JSONB, targettype, list_make1(expr), @@ -3822,6 +3825,8 @@ transformJsonValueExprExt(ParseState *pstate, JsonValueExpr *ve, expr = (Node *) ve; } + else + expr = rawexpr; return expr; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 83c0b59e74..df521d2863 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -3333,30 +3333,42 @@ JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) /********************Interface to pgsql's executor***************************/ bool -JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool isJsonb) +JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool isJsonb, + bool *error) { Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar, - js, isJsonb, true, NULL); + js, isJsonb, !error, NULL); - Assert(!jperIsError(res)); + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + *error = true; return res == jperOk; } Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, - List *vars, bool isJsonb) + bool *error, List *vars, bool isJsonb) { Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonItem *first; bool wrap; JsonValueList found = {0}; - JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY; int count; - jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, true, &found); - Assert(!jperIsError(jper)); + res = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error, &found); + + Assert(error || !jperIsError(res)); + + if (error && jperIsError(res)) + { + *error = true; + *empty = false; + return (Datum) 0; + } count = JsonValueListLength(&found); @@ -3387,12 +3399,20 @@ JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, } if (count > 1) + { + if (error) + { + *error = true; + return (Datum) 0; + } + ereport(ERROR, (errcode(ERRCODE_MORE_THAN_ONE_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 JsonItemToJsonxDatum(first, isJsonb); @@ -3402,7 +3422,8 @@ JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, } JsonItem * -JsonPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars, bool isJsonb) +JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars, + bool isJsonb) { Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonItem *res; @@ -3410,8 +3431,17 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars, bool isJsonb) JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; int count; - jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, true, &found); - Assert(!jperIsError(jper)); + jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error, + &found); + + Assert(error || !jperIsError(jper)); + + if (error && jperIsError(jper)) + { + *error = true; + *empty = false; + return NULL; + } count = JsonValueListLength(&found); @@ -3421,10 +3451,18 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars, bool isJsonb) return NULL; if (count > 1) + { + if (error) + { + *error = true; + return NULL; + } + ereport(ERROR, (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), errmsg("JSON path expression in JSON_VALUE should return " "singleton scalar item"))); + } res = JsonValueListHead(&found); @@ -3438,10 +3476,18 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars, bool isJsonb) } if (!JsonItemIsScalar(res)) + { + if (error) + { + *error = true; + return NULL; + } + ereport(ERROR, (errcode(ERRCODE_JSON_SCALAR_REQUIRED), errmsg("JSON path expression in JSON_VALUE should return " "singleton scalar item"))); + } if (JsonItemIsNull(res)) return NULL; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index caf5f66ead..e7aea15d34 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -811,7 +811,8 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, bool is_jsonb, JsonReturning *returning, struct JsonCoercionsState *coercions, struct JsonCoercionState **pjcstate); -extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); +extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, + struct JsonCoercionsState *); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 4034434173..ecb8499e1e 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -339,11 +339,12 @@ extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, extern Datum JsonItemToJsonxDatum(JsonItem *jsi, bool isJsonb); extern Datum JsonbValueToJsonxDatum(JsonbValue *jbv, bool isJsonb); -extern bool JsonPathExists(Datum jb, JsonPath *path, List *vars, bool isJsonb); +extern bool JsonPathExists(Datum jb, JsonPath *path, + List *vars, bool isJsonb, bool *error); extern Datum JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, - bool *empty, List *vars, bool isJsonb); + bool *empty, bool *error, List *vars, bool isJsonb); extern JsonItem *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, - List *vars, bool isJsonb); + bool *error, List *vars, bool isJsonb); extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); From eced0ca07bce1d2570b8a2d291d5e623e3358842 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 25 Jul 2017 00:46:14 +0300 Subject: [PATCH 40/66] Add JSON_TABLE --- .../pg_stat_statements/pg_stat_statements.c | 2 + src/backend/commands/explain.c | 4 +- src/backend/executor/execExpr.c | 1 + src/backend/executor/execExprInterp.c | 11 + src/backend/executor/nodeTableFuncscan.c | 23 +- src/backend/nodes/copyfuncs.c | 111 ++ src/backend/nodes/equalfuncs.c | 32 + src/backend/nodes/makefuncs.c | 19 + src/backend/nodes/nodeFuncs.c | 27 + src/backend/nodes/outfuncs.c | 32 + src/backend/nodes/readfuncs.c | 34 + src/backend/parser/gram.y | 291 +++++- src/backend/parser/parse_clause.c | 667 ++++++++++++ src/backend/parser/parse_expr.c | 24 +- src/backend/parser/parse_relation.c | 3 +- src/backend/parser/parse_target.c | 3 + src/backend/utils/adt/jsonpath_exec.c | 498 ++++++++- src/backend/utils/adt/ruleutils.c | 284 +++++- src/include/executor/execExpr.h | 4 + src/include/nodes/makefuncs.h | 2 + src/include/nodes/nodes.h | 5 + src/include/nodes/parsenodes.h | 89 ++ src/include/nodes/primnodes.h | 41 +- src/include/parser/kwlist.h | 4 + src/include/utils/jsonpath.h | 5 + src/test/regress/expected/json_sqljson.out | 5 + src/test/regress/expected/jsonb_sqljson.out | 946 ++++++++++++++++++ src/test/regress/sql/json_sqljson.sql | 4 + src/test/regress/sql/jsonb_sqljson.sql | 576 +++++++++++ 29 files changed, 3721 insertions(+), 26 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 206deff18a..958383f51a 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -3003,9 +3003,11 @@ JumbleExpr(pgssJumbleState *jstate, Node *node) { TableFunc *tablefunc = (TableFunc *) node; + APP_JUMB(tablefunc->functype); JumbleExpr(jstate, tablefunc->docexpr); JumbleExpr(jstate, tablefunc->rowexpr); JumbleExpr(jstate, (Node *) tablefunc->colexprs); + JumbleExpr(jstate, (Node *) tablefunc->colvalexprs); } break; case T_TableSampleClause: diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 92969636b7..b61ea629c2 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3097,7 +3097,9 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) break; case T_TableFuncScan: Assert(rte->rtekind == RTE_TABLEFUNC); - objectname = "xmltable"; + objectname = rte->tablefunc ? + rte->tablefunc->functype == TFT_XMLTABLE ? + "xmltable" : "json_table" : NULL; objecttag = "Table Function Name"; break; case T_ValuesScan: diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 3d7d521d92..66af98979e 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2181,6 +2181,7 @@ ExecInitExprRec(Expr *node, ExprState *state, var->typmod = exprTypmod((Node *) argexpr); var->estate = ExecInitExpr(argexpr, state->parent); var->econtext = NULL; + var->mcxt = NULL; var->evaluated = false; var->value = (Datum) 0; var->isnull = true; diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index f638fef524..311e197efe 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4187,6 +4187,7 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, case JSON_BEHAVIOR_NULL: case JSON_BEHAVIOR_UNKNOWN: + case JSON_BEHAVIOR_EMPTY: *is_null = true; return (Datum) 0; @@ -4286,8 +4287,14 @@ EvalJsonPathVar(void *cxt, bool isJsonb, char *varName, int varNameLen, if (!var->evaluated) { + MemoryContext oldcxt = var->mcxt ? + MemoryContextSwitchTo(var->mcxt) : NULL; + var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull); var->evaluated = true; + + if (oldcxt) + MemoryContextSwitchTo(oldcxt); } if (var->isnull) @@ -4584,6 +4591,10 @@ ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext, return BoolGetDatum(res); } + case IS_JSON_TABLE: + *resnull = false; + return item; + default: elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); return (Datum) 0; diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index 45d5f3c424..f76c285ddc 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -28,6 +28,7 @@ #include "executor/tablefunc.h" #include "miscadmin.h" #include "utils/builtins.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/xml.h" @@ -162,8 +163,9 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) scanstate->ss.ps.qual = ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps); - /* Only XMLTABLE is supported currently */ - scanstate->routine = &XmlTableRoutine; + /* Only XMLTABLE and JSON_TABLE are supported currently */ + scanstate->routine = + tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine; scanstate->perTableCxt = AllocSetContextCreate(CurrentMemoryContext, @@ -384,14 +386,17 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc) routine->SetNamespace(tstate, ns_name, ns_uri); } - /* Install the row filter expression into the table builder context */ - value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull); - if (isnull) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("row filter expression must not be null"))); + if (routine->SetRowFilter) + { + /* Install the row filter expression into the table builder context */ + value = ExecEvalExpr(tstate->rowexpr, econtext, &isnull); + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("row filter expression must not be null"))); - routine->SetRowFilter(tstate, TextDatumGetCString(value)); + routine->SetRowFilter(tstate, TextDatumGetCString(value)); + } /* * Install the column filter expressions into the table builder context. diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8fb1bbb3cf..3f3ad971df 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1302,6 +1302,7 @@ _copyTableFunc(const TableFunc *from) { TableFunc *newnode = makeNode(TableFunc); + COPY_SCALAR_FIELD(functype); COPY_NODE_FIELD(ns_uris); COPY_NODE_FIELD(ns_names); COPY_NODE_FIELD(docexpr); @@ -1312,7 +1313,9 @@ _copyTableFunc(const TableFunc *from) COPY_NODE_FIELD(colcollations); COPY_NODE_FIELD(colexprs); COPY_NODE_FIELD(coldefexprs); + COPY_NODE_FIELD(colvalexprs); COPY_BITMAPSET_FIELD(notnulls); + COPY_NODE_FIELD(plan); COPY_SCALAR_FIELD(ordinalitycol); COPY_LOCATION_FIELD(location); @@ -2520,6 +2523,99 @@ _copyJsonArgument(const JsonArgument *from) return newnode; } +/* + * _copyJsonTable + */ +static JsonTable * +_copyJsonTable(const JsonTable *from) +{ + JsonTable *newnode = makeNode(JsonTable); + + COPY_NODE_FIELD(common); + COPY_NODE_FIELD(columns); + COPY_NODE_FIELD(plan); + COPY_NODE_FIELD(on_error); + COPY_NODE_FIELD(alias); + COPY_SCALAR_FIELD(location); + + return newnode; +} + +/* + * _copyJsonTableColumn + */ +static JsonTableColumn * +_copyJsonTableColumn(const JsonTableColumn *from) +{ + JsonTableColumn *newnode = makeNode(JsonTableColumn); + + COPY_SCALAR_FIELD(coltype); + COPY_STRING_FIELD(name); + COPY_NODE_FIELD(typeName); + COPY_STRING_FIELD(pathspec); + COPY_STRING_FIELD(pathname); + COPY_SCALAR_FIELD(format); + COPY_SCALAR_FIELD(wrapper); + COPY_SCALAR_FIELD(omit_quotes); + COPY_NODE_FIELD(columns); + COPY_NODE_FIELD(on_empty); + COPY_NODE_FIELD(on_error); + COPY_SCALAR_FIELD(location); + + return newnode; +} + +/* + * _copyJsonTablePlan + */ +static JsonTablePlan * +_copyJsonTablePlan(const JsonTablePlan *from) +{ + JsonTablePlan *newnode = makeNode(JsonTablePlan); + + COPY_SCALAR_FIELD(plan_type); + COPY_SCALAR_FIELD(join_type); + COPY_STRING_FIELD(pathname); + COPY_NODE_FIELD(plan1); + COPY_NODE_FIELD(plan2); + COPY_SCALAR_FIELD(location); + + return newnode; +} + +/* + * _copyJsonTableParentNode + */ +static JsonTableParentNode * +_copyJsonTableParentNode(const JsonTableParentNode *from) +{ + JsonTableParentNode *newnode = makeNode(JsonTableParentNode); + + COPY_NODE_FIELD(path); + COPY_STRING_FIELD(name); + COPY_NODE_FIELD(child); + COPY_SCALAR_FIELD(outerJoin); + COPY_SCALAR_FIELD(colMin); + COPY_SCALAR_FIELD(colMax); + + return newnode; +} + +/* + * _copyJsonTableSiblingNode + */ +static JsonTableSiblingNode * +_copyJsonTableSiblingNode(const JsonTableSiblingNode *from) +{ + JsonTableSiblingNode *newnode = makeNode(JsonTableSiblingNode); + + COPY_NODE_FIELD(larg); + COPY_NODE_FIELD(rarg); + COPY_SCALAR_FIELD(cross); + + return newnode; +} + /* **************************************************************** * pathnodes.h copy functions * @@ -5475,6 +5571,21 @@ copyObjectImpl(const void *from) case T_JsonItemCoercions: retval = _copyJsonItemCoercions(from); break; + case T_JsonTable: + retval = _copyJsonTable(from); + break; + case T_JsonTableColumn: + retval = _copyJsonTableColumn(from); + break; + case T_JsonTablePlan: + retval = _copyJsonTablePlan(from); + break; + case T_JsonTableParentNode: + retval = _copyJsonTableParentNode(from); + break; + case T_JsonTableSiblingNode: + retval = _copyJsonTableSiblingNode(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 8016c1c758..87c76ee4d7 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -120,6 +120,7 @@ _equalRangeVar(const RangeVar *a, const RangeVar *b) static bool _equalTableFunc(const TableFunc *a, const TableFunc *b) { + COMPARE_SCALAR_FIELD(functype); COMPARE_NODE_FIELD(ns_uris); COMPARE_NODE_FIELD(ns_names); COMPARE_NODE_FIELD(docexpr); @@ -130,13 +131,38 @@ _equalTableFunc(const TableFunc *a, const TableFunc *b) COMPARE_NODE_FIELD(colcollations); COMPARE_NODE_FIELD(colexprs); COMPARE_NODE_FIELD(coldefexprs); + COMPARE_NODE_FIELD(colvalexprs); COMPARE_BITMAPSET_FIELD(notnulls); + COMPARE_NODE_FIELD(plan); COMPARE_SCALAR_FIELD(ordinalitycol); COMPARE_LOCATION_FIELD(location); return true; } +static bool +_equalJsonTableParentNode(const JsonTableParentNode *a, const JsonTableParentNode *b) +{ + COMPARE_NODE_FIELD(path); + COMPARE_STRING_FIELD(name); + COMPARE_NODE_FIELD(child); + COMPARE_SCALAR_FIELD(outerJoin); + COMPARE_SCALAR_FIELD(colMin); + COMPARE_SCALAR_FIELD(colMax); + + return true; +} + +static bool +_equalJsonTableSiblingNode(const JsonTableSiblingNode *a, const JsonTableSiblingNode *b) +{ + COMPARE_NODE_FIELD(larg); + COMPARE_NODE_FIELD(rarg); + COMPARE_SCALAR_FIELD(cross); + + return true; +} + static bool _equalIntoClause(const IntoClause *a, const IntoClause *b) { @@ -3301,6 +3327,12 @@ equal(const void *a, const void *b) case T_JsonItemCoercions: retval = _equalJsonItemCoercions(a, b); break; + case T_JsonTableParentNode: + retval = _equalJsonTableParentNode(a, b); + break; + case T_JsonTableSiblingNode: + retval = _equalJsonTableSiblingNode(a, b); + break; /* * RELATION NODES diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 64f88b9cd0..e96e9f0154 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -794,6 +794,25 @@ makeJsonBehavior(JsonBehaviorType type, Node *default_expr) return behavior; } +/* + * makeJsonTableJoinedPlan - + * creates a joined JsonTablePlan node + */ +Node * +makeJsonTableJoinedPlan(JsonTablePlanJoinType type, Node *plan1, Node *plan2, + int location) +{ + JsonTablePlan *n = makeNode(JsonTablePlan); + + n->plan_type = JSTP_JOINED; + n->join_type = type; + n->plan1 = castNode(JsonTablePlan, plan1); + n->plan2 = castNode(JsonTablePlan, plan2); + n->location = location; + + return (Node *) n; +} + /* * makeJsonEncoding - * converts JSON encoding name to enum JsonEncoding diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index b312c59e44..4c32225e4b 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2309,6 +2309,8 @@ expression_tree_walker(Node *node, return true; if (walker(tf->coldefexprs, context)) return true; + if (walker(tf->colvalexprs, context)) + return true; } break; case T_JsonValueExpr: @@ -3211,6 +3213,7 @@ expression_tree_mutator(Node *node, MUTATE(newnode->rowexpr, tf->rowexpr, Node *); MUTATE(newnode->colexprs, tf->colexprs, List *); MUTATE(newnode->coldefexprs, tf->coldefexprs, List *); + MUTATE(newnode->colvalexprs, tf->colvalexprs, List *); return (Node *) newnode; } break; @@ -4033,6 +4036,30 @@ raw_expression_tree_walker(Node *node, return true; } break; + case T_JsonTable: + { + JsonTable *jt = (JsonTable *) node; + + if (walker(jt->common, context)) + return true; + if (walker(jt->columns, context)) + return true; + } + break; + case T_JsonTableColumn: + { + JsonTableColumn *jtc = (JsonTableColumn *) node; + + if (walker(jtc->typeName, context)) + return true; + if (walker(jtc->on_empty, context)) + return true; + if (walker(jtc->on_error, context)) + return true; + if (jtc->coltype == JTC_NESTED && walker(jtc->columns, 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 f39624303b..00b2944bae 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1026,6 +1026,7 @@ _outTableFunc(StringInfo str, const TableFunc *node) { WRITE_NODE_TYPE("TABLEFUNC"); + WRITE_ENUM_FIELD(functype, TableFuncType); WRITE_NODE_FIELD(ns_uris); WRITE_NODE_FIELD(ns_names); WRITE_NODE_FIELD(docexpr); @@ -1036,7 +1037,9 @@ _outTableFunc(StringInfo str, const TableFunc *node) WRITE_NODE_FIELD(colcollations); WRITE_NODE_FIELD(colexprs); WRITE_NODE_FIELD(coldefexprs); + WRITE_NODE_FIELD(colvalexprs); WRITE_BITMAPSET_FIELD(notnulls); + WRITE_NODE_FIELD(plan); WRITE_INT_FIELD(ordinalitycol); WRITE_LOCATION_FIELD(location); } @@ -1778,6 +1781,29 @@ _outJsonIsPredicateOpts(StringInfo str, const JsonIsPredicateOpts *node) WRITE_BOOL_FIELD(unique_keys); } +static void +_outJsonTableParentNode(StringInfo str, const JsonTableParentNode *node) +{ + WRITE_NODE_TYPE("JSONTABPNODE"); + + WRITE_NODE_FIELD(path); + WRITE_STRING_FIELD(name); + WRITE_NODE_FIELD(child); + WRITE_BOOL_FIELD(outerJoin); + WRITE_INT_FIELD(colMin); + WRITE_INT_FIELD(colMax); +} + +static void +_outJsonTableSiblingNode(StringInfo str, const JsonTableSiblingNode *node) +{ + WRITE_NODE_TYPE("JSONTABSNODE"); + + WRITE_NODE_FIELD(larg); + WRITE_NODE_FIELD(rarg); + WRITE_BOOL_FIELD(cross); +} + /***************************************************************************** * * Stuff from pathnodes.h. @@ -4390,6 +4416,12 @@ outNode(StringInfo str, const void *obj) case T_JsonItemCoercions: _outJsonItemCoercions(str, obj); break; + case T_JsonTableParentNode: + _outJsonTableParentNode(str, obj); + break; + case T_JsonTableSiblingNode: + _outJsonTableSiblingNode(str, obj); + break; default: diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index d7075e735b..ccab467b15 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -492,6 +492,7 @@ _readTableFunc(void) { READ_LOCALS(TableFunc); + READ_ENUM_FIELD(functype, TableFuncType); READ_NODE_FIELD(ns_uris); READ_NODE_FIELD(ns_names); READ_NODE_FIELD(docexpr); @@ -502,7 +503,9 @@ _readTableFunc(void) READ_NODE_FIELD(colcollations); READ_NODE_FIELD(colexprs); READ_NODE_FIELD(coldefexprs); + READ_NODE_FIELD(colvalexprs); READ_BITMAPSET_FIELD(notnulls); + READ_NODE_FIELD(plan); READ_INT_FIELD(ordinalitycol); READ_LOCATION_FIELD(location); @@ -1417,6 +1420,33 @@ _readJsonExpr(void) READ_DONE(); } +static JsonTableParentNode * +_readJsonTableParentNode(void) +{ + READ_LOCALS(JsonTableParentNode); + + READ_NODE_FIELD(path); + READ_STRING_FIELD(name); + READ_NODE_FIELD(child); + READ_BOOL_FIELD(outerJoin); + READ_INT_FIELD(colMin); + READ_INT_FIELD(colMax); + + READ_DONE(); +} + +static JsonTableSiblingNode * +_readJsonTableSiblingNode(void) +{ + READ_LOCALS(JsonTableSiblingNode); + + READ_NODE_FIELD(larg); + READ_NODE_FIELD(rarg); + READ_BOOL_FIELD(cross); + + READ_DONE(); +} + /* * _readJsonCoercion */ @@ -2944,6 +2974,10 @@ parseNodeString(void) return_value = _readJsonCoercion(); else if (MATCH("JSONITEMCOERCIONS", 17)) return_value = _readJsonItemCoercions(); + else if (MATCH("JSONTABPNODE", 12)) + return_value = _readJsonTableParentNode(); + else if (MATCH("JSONTABSNODE", 12)) + return_value = _readJsonTableSiblingNode(); else { elog(ERROR, "badly formatted node string \"%.32s\"...", token); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 29ab36681c..6771aeccb1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -618,9 +618,29 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_aggregate_constructor json_array_aggregate_constructor json_path_specification + json_table + json_table_column_definition + json_table_ordinality_column_definition + json_table_regular_column_definition + json_table_formatted_column_definition + json_table_nested_columns + json_table_plan_clause_opt + json_table_specific_plan + json_table_plan + json_table_plan_simple + json_table_plan_parent_child + json_table_plan_outer + json_table_plan_inner + json_table_plan_sibling + json_table_plan_union + json_table_plan_cross + json_table_plan_primary + json_table_default_plan %type json_arguments json_passing_clause_opt + json_table_columns_clause + json_table_column_definition_list json_name_and_value_list json_value_expr_list json_array_aggregate_order_by_clause_opt @@ -629,9 +649,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type json_table_path_name json_as_path_name_clause_opt + json_table_column_path_specification_clause_opt %type json_encoding json_encoding_clause_opt + json_table_default_plan_choices + json_table_default_plan_inner_outer + json_table_default_plan_union_cross json_wrapper_clause_opt json_wrapper_behavior json_conditional_or_unconditional_opt @@ -645,6 +669,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_behavior_true json_behavior_false json_behavior_unknown + json_behavior_empty json_behavior_empty_array json_behavior_empty_object json_behavior_default @@ -652,6 +677,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_query_behavior json_exists_error_behavior json_exists_error_clause_opt + json_table_error_behavior + json_table_error_clause_opt %type json_value_on_behavior_clause_opt json_query_on_behavior_clause_opt @@ -723,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG - JSON_QUERY JSON_VALUE + JSON_QUERY JSON_TABLE JSON_VALUE KEY KEYS KEEP @@ -733,7 +760,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE - NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NONE + NAME_P NAMES NATIONAL NATURAL NCHAR NESTED NEW NEXT NO NONE NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NULLS_P NUMERIC @@ -741,7 +768,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ORDER ORDINALITY OTHERS OUT_P OUTER_P OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY + PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PATH PLACING PLAN PLANS POLICY POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION @@ -832,7 +859,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); */ %nonassoc UNBOUNDED /* ideally should 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 +%nonassoc COLUMNS FALSE_P KEEP OMIT PASSING TRUE_P UNKNOWN %nonassoc IDENT GENERATED NULL_P PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' @@ -857,6 +884,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); /* kluge to keep xml_whitespace_option from causing shift/reduce conflicts */ %right PRESERVE STRIP_P +%nonassoc json_table_column +%nonassoc NESTED +%left PATH + %nonassoc empty_json_unique %left WITHOUT WITH_LA_UNIQUE @@ -12009,6 +12040,19 @@ table_ref: relation_expr opt_alias_clause $2->alias = $4; $$ = (Node *) $2; } + | json_table opt_alias_clause + { + JsonTable *jt = castNode(JsonTable, $1); + jt->alias = $2; + $$ = (Node *) jt; + } + | LATERAL_P json_table opt_alias_clause + { + JsonTable *jt = castNode(JsonTable, $2); + jt->alias = $3; + jt->lateral = true; + $$ = (Node *) jt; + } ; @@ -12511,6 +12555,8 @@ xmltable_column_option_el: { $$ = makeDefElem("is_not_null", (Node *) makeInteger(true), @1); } | NULL_P { $$ = makeDefElem("is_not_null", (Node *) makeInteger(false), @1); } + | PATH b_expr + { $$ = makeDefElem("path", $2, @1); } ; xml_namespace_list: @@ -14931,6 +14977,10 @@ json_behavior_unknown: UNKNOWN { $$ = makeJsonBehavior(JSON_BEHAVIOR_UNKNOWN, NULL); } ; +json_behavior_empty: + EMPTY_P { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_OBJECT, NULL); } + ; + json_behavior_empty_array: EMPTY_P ARRAY { $$ = makeJsonBehavior(JSON_BEHAVIOR_EMPTY_ARRAY, NULL); } ; @@ -15042,6 +15092,235 @@ json_query_on_behavior_clause_opt: { $$.on_empty = NULL; $$.on_error = NULL; } ; +json_table: + JSON_TABLE '(' + json_api_common_syntax + json_table_columns_clause + json_table_plan_clause_opt + json_table_error_clause_opt + ')' + { + JsonTable *n = makeNode(JsonTable); + n->common = (JsonCommon *) $3; + n->columns = $4; + n->plan = (JsonTablePlan *) $5; + n->on_error = $6; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_columns_clause: + COLUMNS '(' json_table_column_definition_list ')' { $$ = $3; } + ; + +json_table_column_definition_list: + json_table_column_definition + { $$ = list_make1($1); } + | json_table_column_definition_list ',' json_table_column_definition + { $$ = lappend($1, $3); } + ; + +json_table_column_definition: + json_table_ordinality_column_definition %prec json_table_column + | json_table_regular_column_definition %prec json_table_column + | json_table_formatted_column_definition %prec json_table_column + | json_table_nested_columns + ; + +json_table_ordinality_column_definition: + ColId FOR ORDINALITY + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_FOR_ORDINALITY; + n->name = $1; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_regular_column_definition: + ColId Typename + json_table_column_path_specification_clause_opt + json_value_on_behavior_clause_opt + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_REGULAR; + n->name = $1; + n->typeName = $2; + n->format.type = JS_FORMAT_DEFAULT; + n->format.encoding = JS_ENC_DEFAULT; + n->wrapper = JSW_NONE; + n->omit_quotes = false; + n->pathspec = $3; + n->on_empty = $4.on_empty; + n->on_error = $4.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_error_behavior: + json_behavior_error + | json_behavior_empty + ; + +json_table_error_clause_opt: + json_table_error_behavior ON ERROR_P { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_column_path_specification_clause_opt: + PATH Sconst { $$ = $2; } + | /* EMPTY */ %prec json_table_column { $$ = NULL; } + ; + +json_table_formatted_column_definition: + ColId Typename FORMAT json_representation + json_table_column_path_specification_clause_opt + json_wrapper_clause_opt + json_quotes_clause_opt + json_query_on_behavior_clause_opt + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_FORMATTED; + n->name = $1; + n->typeName = $2; + n->format = $4; + n->pathspec = $5; + n->wrapper = $6; + if (n->wrapper != JSW_NONE && $7 != 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(@7))); + n->omit_quotes = $7 == JS_QUOTES_OMIT; + n->on_empty = $8.on_empty; + n->on_error = $8.on_error; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_nested_columns: + NESTED path_opt Sconst + json_as_path_name_clause_opt + json_table_columns_clause + { + JsonTableColumn *n = makeNode(JsonTableColumn); + n->coltype = JTC_NESTED; + n->pathspec = $3; + n->pathname = $4; + n->columns = $5; + n->location = @1; + $$ = (Node *) n; + } + ; + +path_opt: + PATH { } + | /* EMPTY */ { } + ; + +json_table_plan_clause_opt: + json_table_specific_plan { $$ = $1; } + | json_table_default_plan { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +json_table_specific_plan: + PLAN '(' json_table_plan ')' { $$ = $3; } + ; + +json_table_plan: + json_table_plan_simple + | json_table_plan_parent_child + | json_table_plan_sibling + ; + +json_table_plan_simple: + json_table_path_name + { + JsonTablePlan *n = makeNode(JsonTablePlan); + n->plan_type = JSTP_SIMPLE; + n->pathname = $1; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_plan_parent_child: + json_table_plan_outer + | json_table_plan_inner + ; + +json_table_plan_outer: + json_table_plan_simple OUTER_P json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_OUTER, $1, $3, @1); } + ; + +json_table_plan_inner: + json_table_plan_simple INNER_P json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_INNER, $1, $3, @1); } + ; + +json_table_plan_sibling: + json_table_plan_union + | json_table_plan_cross + ; + +json_table_plan_union: + json_table_plan_primary UNION json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_UNION, $1, $3, @1); } + | json_table_plan_union UNION json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_UNION, $1, $3, @1); } + ; + +json_table_plan_cross: + json_table_plan_primary CROSS json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_CROSS, $1, $3, @1); } + | json_table_plan_cross CROSS json_table_plan_primary + { $$ = makeJsonTableJoinedPlan(JSTP_CROSS, $1, $3, @1); } + ; + +json_table_plan_primary: + json_table_plan_simple { $$ = $1; } + | '(' json_table_plan ')' + { + castNode(JsonTablePlan, $2)->location = @1; + $$ = $2; + } + ; + +json_table_default_plan: + PLAN DEFAULT '(' json_table_default_plan_choices ')' + { + JsonTablePlan *n = makeNode(JsonTablePlan); + n->plan_type = JSTP_DEFAULT; + n->join_type = $4; + n->location = @1; + $$ = (Node *) n; + } + ; + +json_table_default_plan_choices: + json_table_default_plan_inner_outer { $$ = $1 | JSTP_UNION; } + | json_table_default_plan_inner_outer ',' + json_table_default_plan_union_cross { $$ = $1 | $3; } + | json_table_default_plan_union_cross { $$ = $1 | JSTP_OUTER; } + | json_table_default_plan_union_cross ',' + json_table_default_plan_inner_outer { $$ = $1 | $3; } + ; + +json_table_default_plan_inner_outer: + INNER_P { $$ = JSTP_INNER; } + | OUTER_P { $$ = JSTP_OUTER; } + ; + +json_table_default_plan_union_cross: + UNION { $$ = JSTP_UNION; } + | CROSS { $$ = JSTP_CROSS; } + ; json_output_clause_opt: RETURNING Typename json_format_clause_opt @@ -15811,6 +16090,7 @@ unreserved_keyword: | MOVE | NAME_P | NAMES + | NESTED | NEW | NEXT | NO @@ -15839,6 +16119,8 @@ unreserved_keyword: | PARTITION | PASSING | PASSWORD + | PATH + | PLAN | PLANS | POLICY | PRECEDING @@ -15997,6 +16279,7 @@ col_name_keyword: | JSON_OBJECT | JSON_OBJECTAGG | JSON_QUERY + | JSON_TABLE | JSON_VALUE | LEAST | NATIONAL diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 2a6b2ff153..6e7d1d1f92 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -48,10 +48,20 @@ #include "utils/builtins.h" #include "utils/guc.h" #include "utils/catcache.h" +#include "utils/json.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/rel.h" +/* Context for JSON_TABLE transformation */ +typedef struct JsonTableContext +{ + JsonTable *table; /* untransformed node */ + TableFunc *tablefunc; /* transformed node */ + List *pathNames; /* list of all path and columns names */ + int pathNameId; /* path name id counter */ + Oid contextItemTypid; /* type oid of context item (json/jsonb) */ +} JsonTableContext; /* Convenience macro for the most common makeNamespaceItem() case */ #define makeDefaultNSItem(rte) makeNamespaceItem(rte, true, true, false, true) @@ -102,6 +112,13 @@ static WindowClause *findWindowClause(List *wclist, const char *name); static Node *transformFrameOffset(ParseState *pstate, int frameOptions, Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc, Node *clause); +static JsonTableParentNode * transformJsonTableColumns(ParseState *pstate, + JsonTableContext *cxt, + JsonTablePlan *plan, + List *columns, + char *pathSpec, + char **pathName, + int location); /* @@ -720,6 +737,8 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf) Assert(!pstate->p_lateral_active); pstate->p_lateral_active = true; + tf->functype = TFT_XMLTABLE; + /* Transform and apply typecast to the row-generating expression ... */ Assert(rtf->rowexpr != NULL); tf->rowexpr = coerce_to_specific_type(pstate, @@ -1040,6 +1059,629 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv) return rte; } +/* + * Transform JSON_TABLE column + * - regular column into JSON_VALUE() + * - formatted column into JSON_QUERY() + */ +static Node * +transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr, + List *passingArgs, bool errorOnError) +{ + JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr); + JsonValueExpr *jvexpr = makeNode(JsonValueExpr); + JsonCommon *common = makeNode(JsonCommon); + JsonOutput *output = makeNode(JsonOutput); + + jfexpr->op = jtc->coltype == JTC_REGULAR ? IS_JSON_VALUE : IS_JSON_QUERY; + jfexpr->common = common; + jfexpr->output = output; + jfexpr->on_empty = jtc->on_empty; + jfexpr->on_error = jtc->on_error; + if (!jfexpr->on_error && errorOnError) + jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL); + jfexpr->omit_quotes = jtc->omit_quotes; + jfexpr->wrapper = jtc->wrapper; + jfexpr->location = jtc->location; + + output->typeName = jtc->typeName; + output->returning.format = jtc->format; + + common->pathname = NULL; + common->expr = jvexpr; + common->passing = passingArgs; + + if (jtc->pathspec) + common->pathspec = jtc->pathspec; + else + { + /* Construct default path as '$."column_name"' */ + StringInfoData path; + + initStringInfo(&path); + + appendStringInfoString(&path, "$."); + escape_json(&path, jtc->name); + + common->pathspec = path.data; + } + + jvexpr->expr = (Expr *) contextItemExpr; + jvexpr->format.type = JS_FORMAT_DEFAULT; + jvexpr->format.encoding = JS_ENC_DEFAULT; + + return (Node *) jfexpr; +} + +static bool +isJsonTablePathNameDuplicate(JsonTableContext *cxt, const char *pathname) +{ + ListCell *lc; + + foreach(lc, cxt->pathNames) + { + if (!strcmp(pathname, (const char *) lfirst(lc))) + return true; + } + + return false; +} + +/* Recursively register column name in the path name list. */ +static void +registerJsonTableColumn(JsonTableContext *cxt, char *colname) +{ + if (isJsonTablePathNameDuplicate(cxt, colname)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_ALIAS), + errmsg("duplicate JSON_TABLE column name: %s", colname), + errhint("JSON_TABLE path names and column names shall be " + "distinct from one another"))); + + cxt->pathNames = lappend(cxt->pathNames, colname); +} + +/* Recursively register all nested column names in the path name list. */ +static void +registerAllJsonTableColumns(JsonTableContext *cxt, List *columns) +{ + ListCell *lc; + + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + + if (jtc->coltype == JTC_NESTED) + { + if (jtc->pathname) + registerJsonTableColumn(cxt, jtc->pathname); + + registerAllJsonTableColumns(cxt, jtc->columns); + } + else + { + registerJsonTableColumn(cxt, jtc->name); + } + } +} + +/* Generate a new unique JSON_TABLE path name. */ +static char * +generateJsonTablePathName(JsonTableContext *cxt) +{ + char namebuf[32]; + char *name = namebuf; + + do + { + snprintf(namebuf, sizeof(namebuf), "json_table_path_%d", + ++cxt->pathNameId); + } while (isJsonTablePathNameDuplicate(cxt, name)); + + name = pstrdup(name); + cxt->pathNames = lappend(cxt->pathNames, name); + + return name; +} + +/* Collect sibling path names from plan to the specified list. */ +static void +collectSiblingPathsInJsonTablePlan(JsonTablePlan *plan, List **paths) +{ + if (plan->plan_type == JSTP_SIMPLE) + *paths = lappend(*paths, plan->pathname); + else if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type == JSTP_INNER || + plan->join_type == JSTP_OUTER) + { + Assert(plan->plan1->plan_type == JSTP_SIMPLE); + *paths = lappend(*paths, plan->plan1->pathname); + } + else if (plan->join_type == JSTP_CROSS || + plan->join_type == JSTP_UNION) + { + collectSiblingPathsInJsonTablePlan(plan->plan1, paths); + collectSiblingPathsInJsonTablePlan(plan->plan2, paths); + } + else + elog(ERROR, "invalid JSON_TABLE join type %d", + plan->join_type); + } +} + +/* + * Validate child JSON_TABLE plan by checking that: + * - all nested columns have path names specified + * - all nested columns have corresponding node in the sibling plan + * - plan does not contain duplicate or extra nodes + */ +static void +validateJsonTableChildPlan(ParseState *pstate, JsonTablePlan *plan, + List *columns) +{ + ListCell *lc1; + List *siblings = NIL; + int nchilds = 0; + + if (plan) + collectSiblingPathsInJsonTablePlan(plan, &siblings); + + foreach(lc1, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc1)); + + if (jtc->coltype == JTC_NESTED) + { + ListCell *lc2; + bool found = false; + + if (!jtc->pathname) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("nested JSON_TABLE columns shall contain " + "explicit AS pathname specification if " + "explicit PLAN clause is used"), + parser_errposition(pstate, jtc->location))); + + /* find nested path name in the list of sibling path names */ + foreach(lc2, siblings) + { + if ((found = !strcmp(jtc->pathname, lfirst(lc2)))) + break; + } + + if (!found) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("plan node for nested path %s " + "was not found in plan", jtc->pathname), + parser_errposition(pstate, jtc->location))); + + nchilds++; + } + } + + if (list_length(siblings) > nchilds) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("plan node contains some extra or " + "duplicate sibling nodes"), + parser_errposition(pstate, plan ? plan->location : -1))); +} + +static JsonTableColumn * +findNestedJsonTableColumn(List *columns, const char *pathname) +{ + ListCell *lc; + + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + + if (jtc->coltype == JTC_NESTED && + jtc->pathname && + !strcmp(jtc->pathname, pathname)) + return jtc; + } + + return NULL; +} + +static Node * +transformNestedJsonTableColumn(ParseState *pstate, JsonTableContext *cxt, + JsonTableColumn *jtc, JsonTablePlan *plan) +{ + JsonTableParentNode *node; + char *pathname = jtc->pathname; + + node = transformJsonTableColumns(pstate, cxt, plan, + jtc->columns, jtc->pathspec, + &pathname, jtc->location); + node->name = pstrdup(pathname); + + return (Node *) node; +} + +static Node * +makeJsonTableSiblingJoin(bool cross, Node *lnode, Node *rnode) +{ + JsonTableSiblingNode *join = makeNode(JsonTableSiblingNode); + + join->larg = lnode; + join->rarg = rnode; + join->cross = cross; + + return (Node *) join; +} + +/* + * Recursively transform child JSON_TABLE plan. + * + * Default plan is transformed into a cross/union join of its nested columns. + * Simple and outer/inner plans are transformed into a JsonTableParentNode by + * finding and transforming corresponding nested column. + * Sibling plans are recursively transformed into a JsonTableSiblingNode. + */ +static Node * +transformJsonTableChildPlan(ParseState *pstate, JsonTableContext *cxt, + JsonTablePlan *plan, List *columns) +{ + JsonTableColumn *jtc = NULL; + + if (!plan || plan->plan_type == JSTP_DEFAULT) + { + /* unspecified or default plan */ + Node *res = NULL; + ListCell *lc; + bool cross = plan && (plan->join_type & JSTP_CROSS); + + /* transform all nested columns into cross/union join */ + foreach(lc, columns) + { + JsonTableColumn *jtc = castNode(JsonTableColumn, lfirst(lc)); + Node *node; + + if (jtc->coltype != JTC_NESTED) + continue; + + node = transformNestedJsonTableColumn(pstate, cxt, jtc, plan); + + /* join transformed node with previous sibling nodes */ + res = res ? makeJsonTableSiblingJoin(cross, res, node) : node; + } + + return res; + } + else if (plan->plan_type == JSTP_SIMPLE) + { + jtc = findNestedJsonTableColumn(columns, plan->pathname); + } + else if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type == JSTP_INNER || + plan->join_type == JSTP_OUTER) + { + Assert(plan->plan1->plan_type == JSTP_SIMPLE); + jtc = findNestedJsonTableColumn(columns, plan->plan1->pathname); + } + else + { + Node *node1 = + transformJsonTableChildPlan(pstate, cxt, plan->plan1, columns); + Node *node2 = + transformJsonTableChildPlan(pstate, cxt, plan->plan2, columns); + + return makeJsonTableSiblingJoin(plan->join_type == JSTP_CROSS, + node1, node2); + } + } + else + elog(ERROR, "invalid JSON_TABLE plan type %d", plan->plan_type); + + if (!jtc) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("path name was %s not found in nested columns list", + plan->pathname), + parser_errposition(pstate, plan->location))); + + return transformNestedJsonTableColumn(pstate, cxt, jtc, plan); +} + +/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */ +static void +appendJsonTableColumns(ParseState *pstate, JsonTableContext *cxt, List *columns) +{ + JsonTable *jt = cxt->table; + TableFunc *tf = cxt->tablefunc; + bool errorOnError = jt->on_error && + jt->on_error->btype == JSON_BEHAVIOR_ERROR; + ListCell *col; + + foreach(col, columns) + { + JsonTableColumn *rawc = castNode(JsonTableColumn, lfirst(col)); + Oid typid; + int32 typmod; + Node *colexpr; + + if (rawc->name) + { + /* make sure column names are unique */ + ListCell *colname; + + foreach(colname, tf->colnames) + if (!strcmp((const char *) colname, rawc->name)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("column name \"%s\" is not unique", + rawc->name), + parser_errposition(pstate, rawc->location))); + + tf->colnames = lappend(tf->colnames, + makeString(pstrdup(rawc->name))); + } + + /* + * Determine the type and typmod for the new column. FOR + * ORDINALITY columns are INTEGER by standard; the others are + * user-specified. + */ + switch (rawc->coltype) + { + case JTC_FOR_ORDINALITY: + colexpr = NULL; + typid = INT4OID; + typmod = -1; + break; + + case JTC_REGULAR: + case JTC_FORMATTED: + { + Node *je; + CaseTestExpr *param = makeNode(CaseTestExpr); + + param->collation = InvalidOid; + param->typeId = cxt->contextItemTypid; + param->typeMod = -1; + + je = transformJsonTableColumn(rawc, (Node *) param, + NIL, errorOnError); + + colexpr = transformExpr(pstate, je, EXPR_KIND_FROM_FUNCTION); + assign_expr_collations(pstate, colexpr); + + typid = exprType(colexpr); + typmod = exprTypmod(colexpr); + break; + } + + case JTC_NESTED: + continue; + + default: + elog(ERROR, "unknown JSON_TABLE column type: %d", rawc->coltype); + break; + } + + tf->coltypes = lappend_oid(tf->coltypes, typid); + tf->coltypmods = lappend_int(tf->coltypmods, typmod); + tf->colcollations = lappend_oid(tf->colcollations, + type_is_collatable(typid) + ? DEFAULT_COLLATION_OID + : InvalidOid); + tf->colvalexprs = lappend(tf->colvalexprs, colexpr); + } +} + +/* + * Create transformed JSON_TABLE parent plan node by appending all non-nested + * columns to the TableFunc node and remembering their indices in the + * colvalexprs list. + */ +static JsonTableParentNode * +makeParentJsonTableNode(ParseState *pstate, JsonTableContext *cxt, + char *pathSpec, List *columns) +{ + JsonTableParentNode *node = makeNode(JsonTableParentNode); + + node->path = makeConst(JSONPATHOID, -1, InvalidOid, -1, + DirectFunctionCall1(jsonpath_in, + CStringGetDatum(pathSpec)), + false, false); + + /* save start of column range */ + node->colMin = list_length(cxt->tablefunc->colvalexprs); + + appendJsonTableColumns(pstate, cxt, columns); + + /* save end of column range */ + node->colMax = list_length(cxt->tablefunc->colvalexprs) - 1; + + node->errorOnError = + cxt->table->on_error && + cxt->table->on_error->btype == JSON_BEHAVIOR_ERROR; + + return node; +} + +static JsonTableParentNode * +transformJsonTableColumns(ParseState *pstate, JsonTableContext *cxt, + JsonTablePlan *plan, List *columns, + char *pathSpec, char **pathName, int location) +{ + JsonTableParentNode *node; + JsonTablePlan *childPlan; + bool defaultPlan = !plan || plan->plan_type == JSTP_DEFAULT; + + if (!*pathName) + { + if (cxt->table->plan) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE expression"), + errdetail("JSON_TABLE columns shall contain " + "explicit AS pathname specification if " + "explicit PLAN clause is used"), + parser_errposition(pstate, location))); + + *pathName = generateJsonTablePathName(cxt); + } + + if (defaultPlan) + childPlan = plan; + else + { + /* validate parent and child plans */ + JsonTablePlan *parentPlan; + + if (plan->plan_type == JSTP_JOINED) + { + if (plan->join_type != JSTP_INNER && + plan->join_type != JSTP_OUTER) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("expected INNER or OUTER JSON_TABLE plan node"), + parser_errposition(pstate, plan->location))); + + parentPlan = plan->plan1; + childPlan = plan->plan2; + + Assert(parentPlan->plan_type != JSTP_JOINED); + Assert(parentPlan->pathname); + } + else + { + parentPlan = plan; + childPlan = NULL; + } + + if (strcmp(parentPlan->pathname, *pathName)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid JSON_TABLE plan"), + errdetail("path name mismatch: expected %s but %s is given", + *pathName, parentPlan->pathname), + parser_errposition(pstate, plan->location))); + + + validateJsonTableChildPlan(pstate, childPlan, columns); + } + + /* transform only non-nested columns */ + node = makeParentJsonTableNode(pstate, cxt, pathSpec, columns); + node->name = pstrdup(*pathName); + + if (childPlan || defaultPlan) + { + /* transform recursively nested columns */ + node->child = transformJsonTableChildPlan(pstate, cxt, childPlan, + columns); + if (node->child) + node->outerJoin = !plan || (plan->join_type & JSTP_OUTER); + /* else: default plan case, no children found */ + } + + return node; +} + +/* + * transformJsonTable - + * Transform a raw JsonTable into TableFunc. + * + * Transform the document-generating expression, the row-generating expression, + * the column-generating expressions, and the default value expressions. + */ +static RangeTblEntry * +transformJsonTable(ParseState *pstate, JsonTable *jt) +{ + JsonTableContext cxt; + TableFunc *tf = makeNode(TableFunc); + JsonFuncExpr *jfe = makeNode(JsonFuncExpr); + JsonCommon *jscommon; + JsonTablePlan *plan = jt->plan; + char *rootPathName = jt->common->pathname; + bool is_lateral; + + cxt.table = jt; + cxt.tablefunc = tf; + cxt.pathNames = NIL; + cxt.pathNameId = 0; + + if (rootPathName) + registerJsonTableColumn(&cxt, rootPathName); + + registerAllJsonTableColumns(&cxt, jt->columns); + +#if 0 /* XXX it' unclear from the standard whether root path name is mandatory or not */ + if (plan && plan->plan_type != JSTP_DEFAULT && !rootPathName) + { + /* Assign root path name and create corresponding plan node */ + JsonTablePlan *rootNode = makeNode(JsonTablePlan); + JsonTablePlan *rootPlan = (JsonTablePlan *) + makeJsonTableJoinedPlan(JSTP_OUTER, (Node *) rootNode, + (Node *) plan, jt->location); + + rootPathName = generateJsonTablePathName(&cxt); + + rootNode->plan_type = JSTP_SIMPLE; + rootNode->pathname = rootPathName; + + plan = rootPlan; + } +#endif + + jscommon = copyObject(jt->common); + jscommon->pathspec = pstrdup("$"); + + jfe->op = IS_JSON_TABLE; + jfe->common = jscommon; + jfe->on_error = jt->on_error; + jfe->location = jt->common->location; + + /* + * We make lateral_only names of this level visible, whether or not the + * RangeTableFunc is explicitly marked LATERAL. This is needed for SQL + * spec compliance and seems useful on convenience grounds for all + * functions in FROM. + * + * (LATERAL can't nest within a single pstate level, so we don't need + * save/restore logic here.) + */ + Assert(!pstate->p_lateral_active); + pstate->p_lateral_active = true; + + tf->functype = TFT_JSON_TABLE; + tf->docexpr = transformExpr(pstate, (Node *) jfe, EXPR_KIND_FROM_FUNCTION); + + cxt.contextItemTypid = exprType(tf->docexpr); + + tf->plan = (Node *) transformJsonTableColumns(pstate, &cxt, plan, + jt->columns, + jt->common->pathspec, + &rootPathName, + jt->common->location); + + tf->ordinalitycol = -1; /* undefine ordinality column number */ + tf->location = jt->location; + + pstate->p_lateral_active = false; + + /* + * Mark the RTE as LATERAL if the user said LATERAL explicitly, or if + * there are any lateral cross-references in it. + */ + is_lateral = jt->lateral || contain_vars_of_level((Node *) tf, 0); + + return addRangeTableEntryForTableFunc(pstate, + tf, jt->alias, is_lateral, true); +} + /* * transformFromClauseItem - * Transform a FROM-clause item, adding any required entries to the @@ -1172,6 +1814,31 @@ transformFromClauseItem(ParseState *pstate, Node *n, rte->tablesample = transformRangeTableSample(pstate, rts); return (Node *) rtr; } + else if (IsA(n, JsonTable)) + { + /* JsonTable is transformed into RangeSubselect */ + /* + JsonTable *jt = castNode(JsonTable, n); + RangeSubselect *subselect = transformJsonTable(pstate, jt); + + return transformFromClauseItem(pstate, (Node *) subselect, + top_rte, top_rti, namespace); + */ + RangeTblRef *rtr; + RangeTblEntry *rte; + int rtindex; + + rte = transformJsonTable(pstate, (JsonTable *) n); + /* assume new rte is at end */ + rtindex = list_length(pstate->p_rtable); + Assert(rte == rt_fetch(rtindex, pstate->p_rtable)); + *top_rte = rte; + *top_rti = rtindex; + *namespace = list_make1(makeDefaultNSItem(rte)); + rtr = makeNode(RangeTblRef); + rtr->rtindex = rtindex; + return (Node *) rtr; + } else if (IsA(n, JoinExpr)) { /* A newfangled join expression */ diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index a46edc358c..b7f7d58f1a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4510,7 +4510,7 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) Node *pathspec; JsonFormatType format; - if (func->common->pathname) + if (func->common->pathname && func->op != IS_JSON_TABLE) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("JSON_TABLE path name is not allowed here"), @@ -4556,14 +4556,13 @@ transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) transformJsonPassingArgs(pstate, format, func->common->passing, &jsexpr->passing); - if (func->op != IS_JSON_EXISTS) + if (func->op != IS_JSON_EXISTS && func->op != IS_JSON_TABLE) 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); + func->op == IS_JSON_EXISTS ? JSON_BEHAVIOR_FALSE : + func->op == IS_JSON_TABLE ? JSON_BEHAVIOR_EMPTY : JSON_BEHAVIOR_NULL); return jsexpr; } @@ -4817,6 +4816,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) jsexpr->returning.typmod = -1; break; + + case IS_JSON_TABLE: + jsexpr->returning.format.type = JS_FORMAT_DEFAULT; + jsexpr->returning.format.encoding = JS_ENC_DEFAULT; + jsexpr->returning.format.location = -1; + jsexpr->returning.typid = exprType(contextItemExpr); + jsexpr->returning.typmod = -1; + + if (jsexpr->returning.typid != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("JSON_TABLE() is not yet implemented for json type"), + parser_errposition(pstate, func->location))); + + break; } return (Node *) jsexpr; diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 77a48b039d..abf908ee51 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1708,7 +1708,8 @@ addRangeTableEntryForTableFunc(ParseState *pstate, bool inFromCl) { RangeTblEntry *rte = makeNode(RangeTblEntry); - char *refname = alias ? alias->aliasname : pstrdup("xmltable"); + char *refname = alias ? alias->aliasname : + pstrdup(tf->functype == TFT_XMLTABLE ? "xmltable" : "json_table"); Alias *eref; int numaliases; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 62b9713a3c..0c7993d571 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1952,6 +1952,9 @@ FigureColnameInternal(Node *node, char **name) case IS_JSON_EXISTS: *name = "json_exists"; return 2; + case IS_JSON_TABLE: + *name = "json_table"; + return 2; } break; default: diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index df521d2863..34ab5c82ff 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -61,11 +61,14 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" +#include "executor/execExpr.h" #include "funcapi.h" #include "lib/stringinfo.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "regex/regex.h" #include "utils/builtins.h" +#include "utils/date.h" #include "utils/datetime.h" #include "utils/datum.h" #include "utils/formatting.h" @@ -74,7 +77,8 @@ #include "utils/json.h" #include "utils/jsonapi.h" #include "utils/jsonpath.h" -#include "utils/date.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/timestamp.h" #include "utils/varlena.h" @@ -206,6 +210,59 @@ typedef struct JsonPathUserFuncContext bool silent; /* error suppression flag */ } JsonPathUserFuncContext; +/* Structures for JSON_TABLE execution */ +typedef struct JsonTableScanState JsonTableScanState; +typedef struct JsonTableJoinState JsonTableJoinState; + +struct JsonTableScanState +{ + JsonTableScanState *parent; + JsonTableJoinState *nested; + MemoryContext mcxt; + JsonPath *path; + List *args; + JsonValueList found; + JsonValueListIterator iter; + Datum current; + int ordinal; + bool currentIsNull; + bool outerJoin; + bool errorOnError; + bool advanceNested; + bool reset; +}; + +struct JsonTableJoinState +{ + union + { + struct + { + JsonTableJoinState *left; + JsonTableJoinState *right; + bool cross; + bool advanceRight; + } join; + JsonTableScanState scan; + } u; + bool is_join; +}; + +/* random number to identify JsonTableContext */ +#define JSON_TABLE_CONTEXT_MAGIC 418352867 + +typedef struct JsonTableContext +{ + int magic; + struct + { + ExprState *expr; + JsonTableScanState *scan; + } *colexprs; + JsonTableScanState root; + bool empty; +} JsonTableContext; + /* strict/lax flags is decomposed into four [un]wrap/error flags */ #define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode) #define jspAutoUnwrap(cxt) ((cxt)->laxMode) @@ -335,6 +392,7 @@ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, int32 *index); static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt, JsonItem *jsi, int32 id); +static void JsonValueListClear(JsonValueList *jvl); static void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv); static int JsonValueListLength(const JsonValueList *jvl); static bool JsonValueListIsEmpty(JsonValueList *jvl); @@ -374,6 +432,11 @@ static void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item); static void popJsonItem(JsonItemStack *stack); +static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, + Node *plan, JsonTableScanState *parent); +static bool JsonTableNextRow(JsonTableScanState *scan); + + /****************** User interface to JsonPath executor ********************/ /* @@ -2730,6 +2793,14 @@ setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id) return baseObject; } +static void +JsonValueListClear(JsonValueList *jvl) +{ + jvl->head = NULL; + jvl->tail = NULL; + jvl->length = 0; +} + static void JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi) { @@ -3602,3 +3673,428 @@ JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonItem *res, "casted to supported jsonpath types."))); } } + +/************************ JSON_TABLE functions ***************************/ + +/* + * Returns private data from executor state. Ensure validity by check with + * MAGIC number. + */ +static inline JsonTableContext * +GetJsonTableContext(TableFuncScanState *state, const char *fname) +{ + JsonTableContext *result; + + if (!IsA(state, TableFuncScanState)) + elog(ERROR, "%s called with invalid TableFuncScanState", fname); + result = (JsonTableContext *) state->opaque; + if (result->magic != JSON_TABLE_CONTEXT_MAGIC) + elog(ERROR, "%s called with invalid TableFuncScanState", fname); + + return result; +} + +/* Recursively initialize JSON_TABLE scan state */ +static void +JsonTableInitScanState(JsonTableContext *cxt, JsonTableScanState *scan, + JsonTableParentNode *node, JsonTableScanState *parent, + List *args, MemoryContext mcxt) +{ + int i; + + scan->parent = parent; + scan->outerJoin = node->outerJoin; + scan->errorOnError = node->errorOnError; + scan->path = DatumGetJsonPathP(node->path->constvalue); + scan->args = args; + scan->mcxt = AllocSetContextCreate(mcxt, "JsonTableContext", + ALLOCSET_DEFAULT_SIZES); + scan->nested = node->child ? + JsonTableInitPlanState(cxt, node->child, scan) : NULL; + scan->current = PointerGetDatum(NULL); + scan->currentIsNull = true; + + for (i = node->colMin; i <= node->colMax; i++) + cxt->colexprs[i].scan = scan; +} + +/* Recursively initialize JSON_TABLE scan state */ +static JsonTableJoinState * +JsonTableInitPlanState(JsonTableContext *cxt, Node *plan, + JsonTableScanState *parent) +{ + JsonTableJoinState *state = palloc0(sizeof(*state)); + + if (IsA(plan, JsonTableSiblingNode)) + { + JsonTableSiblingNode *join = castNode(JsonTableSiblingNode, plan); + + state->is_join = true; + state->u.join.cross = join->cross; + state->u.join.left = JsonTableInitPlanState(cxt, join->larg, parent); + state->u.join.right = JsonTableInitPlanState(cxt, join->rarg, parent); + } + else + { + JsonTableParentNode *node = castNode(JsonTableParentNode, plan); + + state->is_join = false; + + JsonTableInitScanState(cxt, &state->u.scan, node, parent, + parent->args, parent->mcxt); + } + + return state; +} + +/* + * JsonTableInitOpaque + * Fill in TableFuncScanState->opaque for JsonTable processor + */ +static void +JsonTableInitOpaque(TableFuncScanState *state, int natts) +{ + JsonTableContext *cxt; + PlanState *ps = &state->ss.ps; + TableFuncScan *tfs = castNode(TableFuncScan, ps->plan); + TableFunc *tf = tfs->tablefunc; + JsonExpr *ci = castNode(JsonExpr, tf->docexpr); + JsonTableParentNode *root = castNode(JsonTableParentNode, tf->plan); + List *args = NIL; + ListCell *lc; + int i; + + cxt = palloc0(sizeof(JsonTableContext)); + cxt->magic = JSON_TABLE_CONTEXT_MAGIC; + + if (list_length(ci->passing.values) > 0) + { + ListCell *exprlc; + ListCell *namelc; + + forboth(exprlc, ci->passing.values, + namelc, ci->passing.names) + { + Expr *expr = (Expr *) lfirst(exprlc); + Value *name = (Value *) lfirst(namelc); + JsonPathVariableEvalContext *var = palloc(sizeof(*var)); + + var->name = pstrdup(name->val.str); + var->typid = exprType((Node *) expr); + var->typmod = exprTypmod((Node *) expr); + var->estate = ExecInitExpr(expr, ps); + var->econtext = ps->ps_ExprContext; + var->mcxt = CurrentMemoryContext; + var->evaluated = false; + var->value = (Datum) 0; + var->isnull = true; + + args = lappend(args, var); + } + } + + cxt->colexprs = palloc(sizeof(*cxt->colexprs) * + list_length(tf->colvalexprs)); + + JsonTableInitScanState(cxt, &cxt->root, root, NULL, args, + CurrentMemoryContext); + + i = 0; + + foreach(lc, tf->colvalexprs) + { + Expr *expr = lfirst(lc); + + cxt->colexprs[i].expr = + ExecInitExprWithCaseValue(expr, ps, + &cxt->colexprs[i].scan->current, + &cxt->colexprs[i].scan->currentIsNull); + + i++; + } + + state->opaque = cxt; +} + +/* Reset scan iterator to the beginning of the item list */ +static void +JsonTableRescan(JsonTableScanState *scan) +{ + JsonValueListInitIterator(&scan->found, &scan->iter); + scan->current = PointerGetDatum(NULL); + scan->currentIsNull = true; + scan->advanceNested = false; + scan->ordinal = 0; +} + +/* Reset context item of a scan, execute JSON path and reset a scan */ +static void +JsonTableResetContextItem(JsonTableScanState *scan, Datum item) +{ + MemoryContext oldcxt; + JsonPathExecResult res; + Jsonx *js = (Jsonx *) DatumGetJsonbP(item); + + JsonValueListClear(&scan->found); + + MemoryContextResetOnly(scan->mcxt); + + oldcxt = MemoryContextSwitchTo(scan->mcxt); + + res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js, true, + scan->errorOnError, &scan->found); + + MemoryContextSwitchTo(oldcxt); + + if (jperIsError(res)) + { + Assert(!scan->errorOnError); + JsonValueListClear(&scan->found); /* EMPTY ON ERROR case */ + } + + JsonTableRescan(scan); +} + +/* + * JsonTableSetDocument + * Install the input document + */ +static void +JsonTableSetDocument(TableFuncScanState *state, Datum value) +{ + JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableSetDocument"); + + JsonTableResetContextItem(&cxt->root, value); +} + +/* Recursively reset scan and its child nodes */ +static void +JsonTableRescanRecursive(JsonTableJoinState *state) +{ + if (state->is_join) + { + JsonTableRescanRecursive(state->u.join.left); + JsonTableRescanRecursive(state->u.join.right); + state->u.join.advanceRight = false; + } + else + { + JsonTableRescan(&state->u.scan); + if (state->u.scan.nested) + JsonTableRescanRecursive(state->u.scan.nested); + } +} + +/* + * Fetch next row from a cross/union joined scan. + * + * Returned false at the end of a scan, true otherwise. + */ +static bool +JsonTableNextJoinRow(JsonTableJoinState *state) +{ + if (!state->is_join) + return JsonTableNextRow(&state->u.scan); + + if (state->u.join.advanceRight) + { + /* fetch next inner row */ + if (JsonTableNextJoinRow(state->u.join.right)) + return true; + + /* inner rows are exhausted */ + if (state->u.join.cross) + state->u.join.advanceRight = false; /* next outer row */ + else + return false; /* end of scan */ + } + + while (!state->u.join.advanceRight) + { + /* fetch next outer row */ + bool left = JsonTableNextJoinRow(state->u.join.left); + + if (state->u.join.cross) + { + if (!left) + return false; /* end of scan */ + + JsonTableRescanRecursive(state->u.join.right); + + if (!JsonTableNextJoinRow(state->u.join.right)) + continue; /* next outer row */ + + state->u.join.advanceRight = true; /* next inner row */ + } + else if (!left) + { + if (!JsonTableNextJoinRow(state->u.join.right)) + return false; /* end of scan */ + + state->u.join.advanceRight = true; /* next inner row */ + } + + break; + } + + return true; +} + +/* Recursively set 'reset' flag of scan and its child nodes */ +static void +JsonTableJoinReset(JsonTableJoinState *state) +{ + if (state->is_join) + { + JsonTableJoinReset(state->u.join.left); + JsonTableJoinReset(state->u.join.right); + state->u.join.advanceRight = false; + } + else + { + state->u.scan.reset = true; + state->u.scan.advanceNested = false; + + if (state->u.scan.nested) + JsonTableJoinReset(state->u.scan.nested); + } +} + +/* + * Fetch next row from a simple scan with outer/inner joined nested subscans. + * + * Returned false at the end of a scan, true otherwise. + */ +static bool +JsonTableNextRow(JsonTableScanState *scan) +{ + /* reset context item if requested */ + if (scan->reset) + { + Assert(!scan->parent->currentIsNull); + JsonTableResetContextItem(scan, scan->parent->current); + scan->reset = false; + } + + if (scan->advanceNested) + { + /* fetch next nested row */ + scan->advanceNested = JsonTableNextJoinRow(scan->nested); + + if (scan->advanceNested) + return true; + } + + for (;;) + { + /* fetch next row */ + JsonItem *jbv = JsonValueListNext(&scan->found, &scan->iter); + MemoryContext oldcxt; + + if (!jbv) + { + scan->current = PointerGetDatum(NULL); + scan->currentIsNull = true; + return false; /* end of scan */ + } + + /* set current row item */ + oldcxt = MemoryContextSwitchTo(scan->mcxt); + scan->current = JsonbPGetDatum(JsonItemToJsonb(jbv)); + scan->currentIsNull = false; + MemoryContextSwitchTo(oldcxt); + + scan->ordinal++; + + if (!scan->nested) + break; + + JsonTableJoinReset(scan->nested); + + scan->advanceNested = JsonTableNextJoinRow(scan->nested); + + if (scan->advanceNested || scan->outerJoin) + break; + + /* state->ordinal--; */ /* skip current outer row, reset counter */ + } + + return true; +} + +/* + * JsonTableFetchRow + * Prepare the next "current" tuple for upcoming GetValue calls. + * Returns FALSE if the row-filter expression returned no more rows. + */ +static bool +JsonTableFetchRow(TableFuncScanState *state) +{ + JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableFetchRow"); + + if (cxt->empty) + return false; + + return JsonTableNextRow(&cxt->root); +} + +/* + * JsonTableGetValue + * Return the value for column number 'colnum' for the current row. + * + * This leaks memory, so be sure to reset often the context in which it's + * called. + */ +static Datum +JsonTableGetValue(TableFuncScanState *state, int colnum, + Oid typid, int32 typmod, bool *isnull) +{ + JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableGetValue"); + ExprContext *econtext = state->ss.ps.ps_ExprContext; + ExprState *estate = cxt->colexprs[colnum].expr; + JsonTableScanState *scan = cxt->colexprs[colnum].scan; + Datum result; + + if (scan->currentIsNull) /* NULL from outer/union join */ + { + result = (Datum) 0; + *isnull = true; + } + else if (estate) /* regular column */ + { + result = ExecEvalExpr(estate, econtext, isnull); + } + else + { + result = Int32GetDatum(scan->ordinal); /* ordinality column */ + *isnull = false; + } + + return result; +} + +/* + * JsonTableDestroyOpaque + */ +static void +JsonTableDestroyOpaque(TableFuncScanState *state) +{ + JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableDestroyOpaque"); + + /* not valid anymore */ + cxt->magic = 0; + + state->opaque = NULL; +} + +const TableFuncRoutine JsonbTableRoutine = +{ + JsonTableInitOpaque, + JsonTableSetDocument, + NULL, + NULL, + NULL, + JsonTableFetchRow, + JsonTableGetValue, + JsonTableDestroyOpaque +}; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index a2ed7385a7..e7c15003c4 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -471,6 +471,8 @@ static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); static void get_json_path_spec(Node *path_spec, deparse_context *context, bool showimplicit); +static void get_json_table_columns(TableFunc *tf, JsonTableParentNode *node, + deparse_context *context, bool showimplicit); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -9085,6 +9087,9 @@ get_rule_expr(Node *node, deparse_context *context, case IS_JSON_EXISTS: appendStringInfoString(buf, "JSON_EXISTS("); break; + default: + elog(ERROR, "unexpected JsonExpr type: %d", jexpr->op); + break; } get_rule_expr(jexpr->raw_expr, context, showimplicit); @@ -10114,16 +10119,14 @@ get_sublink_expr(SubLink *sublink, deparse_context *context) /* ---------- - * get_tablefunc - Parse back a table function + * get_xmltable - Parse back a XMLTABLE function * ---------- */ static void -get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) +get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit) { StringInfo buf = context->buf; - /* XMLTABLE is the only existing implementation. */ - appendStringInfoString(buf, "XMLTABLE("); if (tf->ns_uris != NIL) @@ -10214,6 +10217,279 @@ get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) appendStringInfoChar(buf, ')'); } +/* + * get_json_nested_columns - Parse back nested JSON_TABLE columns + */ +static void +get_json_table_nested_columns(TableFunc *tf, Node *node, + deparse_context *context, bool showimplicit, + bool needcomma) +{ + if (IsA(node, JsonTableSiblingNode)) + { + JsonTableSiblingNode *n = (JsonTableSiblingNode *) node; + + get_json_table_nested_columns(tf, n->larg, context, showimplicit, + needcomma); + get_json_table_nested_columns(tf, n->rarg, context, showimplicit, true); + } + else + { + JsonTableParentNode *n = castNode(JsonTableParentNode, node); + + if (needcomma) + appendStringInfoChar(context->buf, ','); + + appendStringInfoChar(context->buf, ' '); + appendContextKeyword(context, "NESTED PATH ", 0, 0, 0); + get_const_expr(n->path, context, -1); + appendStringInfo(context->buf, " AS %s", quote_identifier(n->name)); + get_json_table_columns(tf, n, context, showimplicit); + } +} + +/* + * get_json_table_plan - Parse back a JSON_TABLE plan + */ +static void +get_json_table_plan(TableFunc *tf, Node *node, deparse_context *context, + bool parenthesize) +{ + if (parenthesize) + appendStringInfoChar(context->buf, '('); + + if (IsA(node, JsonTableSiblingNode)) + { + JsonTableSiblingNode *n = (JsonTableSiblingNode *) node; + + get_json_table_plan(tf, n->larg, context, + IsA(n->larg, JsonTableSiblingNode) || + castNode(JsonTableParentNode, n->larg)->child); + + appendStringInfoString(context->buf, n->cross ? " CROSS " : " UNION "); + + get_json_table_plan(tf, n->rarg, context, + IsA(n->rarg, JsonTableSiblingNode) || + castNode(JsonTableParentNode, n->rarg)->child); + } + else + { + JsonTableParentNode *n = castNode(JsonTableParentNode, node); + + appendStringInfoString(context->buf, quote_identifier(n->name)); + + if (n->child) + { + appendStringInfoString(context->buf, + n->outerJoin ? " OUTER " : " INNER "); + get_json_table_plan(tf, n->child, context, + IsA(n->child, JsonTableSiblingNode)); + } + } + + if (parenthesize) + appendStringInfoChar(context->buf, ')'); +} + +/* + * get_json_table_columns - Parse back JSON_TABLE columns + */ +static void +get_json_table_columns(TableFunc *tf, JsonTableParentNode *node, + deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + ListCell *l1; + ListCell *l2; + ListCell *l3; + ListCell *l4; + int colnum = 0; + + l2 = list_head(tf->coltypes); + l3 = list_head(tf->coltypmods); + l4 = list_head(tf->colvalexprs); + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "COLUMNS (", 0, 0, 0); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + foreach(l1, tf->colnames) + { + char *colname = strVal(lfirst(l1)); + JsonExpr *colexpr; + Oid typid; + int32 typmod; + bool ordinality; + + typid = lfirst_oid(l2); + l2 = lnext(l2); + typmod = lfirst_int(l3); + l3 = lnext(l3); + colexpr = castNode(JsonExpr, lfirst(l4)); + l4 = lnext(l4); + + if (colnum < node->colMin) + { + colnum++; + continue; + } + + if (colnum > node->colMax) + break; + + if (colnum > node->colMin) + appendStringInfoString(buf, ", "); + + colnum++; + + ordinality = !colexpr; + + appendContextKeyword(context, "", 0, 0, 0); + + appendStringInfo(buf, "%s %s", quote_identifier(colname), + ordinality ? "FOR ORDINALITY" : + format_type_with_typemod(typid, typmod)); + if (ordinality) + continue; + + if (colexpr->op == IS_JSON_QUERY) + appendStringInfoString(buf, + colexpr->format.type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + appendStringInfoString(buf, " PATH "); + get_const_expr(colexpr->path_spec, context, -1); + + if (colexpr->wrapper == JSW_CONDITIONAL) + appendStringInfo(buf, " WITH CONDITIONAL WRAPPER"); + + if (colexpr->wrapper == JSW_UNCONDITIONAL) + appendStringInfo(buf, " WITH UNCONDITIONAL WRAPPER"); + + if (colexpr->omit_quotes) + appendStringInfo(buf, " OMIT QUOTES"); + + if (colexpr->on_empty.btype != JSON_BEHAVIOR_NULL) + get_json_behavior(&colexpr->on_empty, context, "EMPTY"); + + if (colexpr->on_error.btype != JSON_BEHAVIOR_NULL) + get_json_behavior(&colexpr->on_error, context, "ERROR"); + } + + if (node->child) + get_json_table_nested_columns(tf, node->child, context, showimplicit, + node->colMax >= node->colMin); + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + + appendContextKeyword(context, ")", 0, 0, 0); +} + +/* ---------- + * get_json_table - Parse back a JSON_TABLE function + * ---------- + */ +static void +get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + StringInfo buf = context->buf; + JsonExpr *jexpr = castNode(JsonExpr, tf->docexpr); + JsonTableParentNode *root = castNode(JsonTableParentNode, tf->plan); + + appendStringInfoString(buf, "JSON_TABLE("); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + appendContextKeyword(context, "", 0, 0, 0); + + get_rule_expr(jexpr->raw_expr, context, showimplicit); + + if (jexpr->format.type != JS_FORMAT_DEFAULT) + { + appendStringInfoString(buf, + jexpr->format.type == JS_FORMAT_JSONB ? + " FORMAT JSONB" : " FORMAT JSON"); + + if (jexpr->format.encoding != JS_ENC_DEFAULT) + { + const char *encoding = + jexpr->format.encoding == JS_ENC_UTF16 ? "UTF16" : + jexpr->format.encoding == JS_ENC_UTF32 ? "UTF32" : + "UTF8"; + + appendStringInfo(buf, " ENCODING %s", encoding); + } + } + + appendStringInfoString(buf, ", "); + + get_const_expr(root->path, context, -1); + + appendStringInfo(buf, " AS %s", quote_identifier(root->name)); + + if (jexpr->passing.values) + { + ListCell *lc1, *lc2; + bool needcomma = false; + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PASSING ", 0, 0, 0); + + if (PRETTY_INDENT(context)) + context->indentLevel += PRETTYINDENT_VAR; + + forboth(lc1, jexpr->passing.names, + lc2, jexpr->passing.values) + { + if (needcomma) + appendStringInfoString(buf, ", "); + needcomma = true; + + appendContextKeyword(context, "", 0, 0, 0); + + get_rule_expr((Node *) lfirst(lc2), context, false); + appendStringInfo(buf, " AS %s", + quote_identifier(((Value *) lfirst(lc1))->val.str)); + } + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + } + + get_json_table_columns(tf, root, context, showimplicit); + + appendStringInfoChar(buf, ' '); + appendContextKeyword(context, "PLAN ", 0, 0, 0); + get_json_table_plan(tf, (Node *) root, context, true); + + if (jexpr->on_error.btype != JSON_BEHAVIOR_EMPTY) + get_json_behavior(&jexpr->on_error, context, "ERROR"); + + if (PRETTY_INDENT(context)) + context->indentLevel -= PRETTYINDENT_VAR; + + appendContextKeyword(context, ")", 0, 0, 0); +} + +/* ---------- + * get_tablefunc - Parse back a table function + * ---------- + */ +static void +get_tablefunc(TableFunc *tf, deparse_context *context, bool showimplicit) +{ + /* XMLTABLE and JSON_TABLE are the only existing implementations. */ + + if (tf->functype == TFT_XMLTABLE) + get_xmltable(tf, context, showimplicit); + else if (tf->functype == TFT_JSON_TABLE) + get_json_table(tf, context, showimplicit); +} + /* ---------- * get_from_clause - Parse back a FROM clause * diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index e7aea15d34..179d127fef 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -813,6 +813,10 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonItem *item, bool is_jsonb, struct JsonCoercionState **pjcstate); extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr, struct JsonCoercionsState *); +extern Datum ExecEvalExprPassingCaseValue(ExprState *estate, + ExprContext *econtext, bool *isnull, + Datum caseval_datum, + bool caseval_isnull); extern void ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup); extern Datum ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans, diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index a15d91d5e1..8d17083b41 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -102,6 +102,8 @@ extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_ extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat format); extern JsonBehavior *makeJsonBehavior(JsonBehaviorType type, Node *expr); +extern Node *makeJsonTableJoinedPlan(JsonTablePlanJoinType type, + Node *plan1, Node *plan2, int location); extern Node *makeJsonKeyValue(Node *key, Node *value); extern Node *makeJsonIsPredicate(Node *expr, JsonFormat format, JsonValueType vtype, bool unique_keys); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 238df4ca79..34e9405c61 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -199,6 +199,8 @@ typedef enum NodeTag T_JsonExpr, T_JsonCoercion, T_JsonItemCoercions, + T_JsonTableParentNode, + T_JsonTableSiblingNode, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -488,6 +490,9 @@ typedef enum NodeTag T_JsonFuncExpr, T_JsonIsPredicate, T_JsonExistsPredicate, + T_JsonTable, + T_JsonTableColumn, + T_JsonTablePlan, T_JsonCommon, T_JsonArgument, T_JsonKeyValue, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index bc894050ca..d5d2544c95 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1476,6 +1476,18 @@ typedef enum JsonQuotes JS_QUOTES_OMIT /* OMIT QUOTES */ } JsonQuotes; +/* + * JsonTableColumnType - + * enumeration of JSON_TABLE column types + */ +typedef enum +{ + JTC_FOR_ORDINALITY, + JTC_REGULAR, + JTC_FORMATTED, + JTC_NESTED, +} JsonTableColumnType; + /* * JsonPathSpec - * representation of JSON path constant @@ -1546,6 +1558,83 @@ typedef struct JsonFuncExpr int location; /* token location, or -1 if unknown */ } JsonFuncExpr; +/* + * JsonTableColumn - + * untransformed representation of JSON_TABLE column + */ +typedef struct JsonTableColumn +{ + NodeTag type; + JsonTableColumnType coltype; /* column type */ + char *name; /* column name */ + TypeName *typeName; /* column type name */ + JsonPathSpec pathspec; /* path specification, if any */ + char *pathname; /* path name, if any */ + JsonFormat format; /* JSON format clause, if specified */ + JsonWrapper wrapper; /* WRAPPER behavior for formatted columns */ + bool omit_quotes; /* omit or keep quotes on scalar strings? */ + List *columns; /* nested columns */ + JsonBehavior *on_empty; /* ON EMPTY behavior */ + JsonBehavior *on_error; /* ON ERROR behavior */ + int location; /* token location, or -1 if unknown */ +} JsonTableColumn; + +/* + * JsonTablePlanType - + * flags for JSON_TABLE plan node types representation + */ +typedef enum JsonTablePlanType +{ + JSTP_DEFAULT, + JSTP_SIMPLE, + JSTP_JOINED, +} JsonTablePlanType; + +/* + * JsonTablePlanJoinType - + * flags for JSON_TABLE join types representation + */ +typedef enum JsonTablePlanJoinType +{ + JSTP_INNER = 0x01, + JSTP_OUTER = 0x02, + JSTP_CROSS = 0x04, + JSTP_UNION = 0x08, +} JsonTablePlanJoinType; + +typedef struct JsonTablePlan JsonTablePlan; + +/* + * JsonTablePlan - + * untransformed representation of JSON_TABLE plan node + */ +struct JsonTablePlan +{ + NodeTag type; + JsonTablePlanType plan_type; /* plan type */ + JsonTablePlanJoinType join_type; /* join type (for joined plan only) */ + JsonTablePlan *plan1; /* first joined plan */ + JsonTablePlan *plan2; /* second joined plan */ + char *pathname; /* path name (for simple plan only) */ + int location; /* token location, or -1 if unknown */ +}; + +/* + * JsonTable - + * untransformed representation of JSON_TABLE + */ +typedef struct JsonTable +{ + NodeTag type; + JsonCommon *common; /* common JSON path syntax fields */ + List *columns; /* list of JsonTableColumn */ + JsonTablePlan *plan; /* join plan, if specified */ + JsonBehavior *on_error; /* ON ERROR behavior, if specified */ + Alias *alias; /* table alias in FROM clause */ + bool lateral; /* does it have LATERAL prefix? */ + int location; /* token location, or -1 if unknown */ +} JsonTable; + /* * JsonValueType - * representation of JSON item type in IS JSON predicate diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 52d6802c5c..2eabccae07 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -73,6 +73,12 @@ typedef struct RangeVar int location; /* token location, or -1 if unknown */ } RangeVar; +typedef enum TableFuncType +{ + TFT_XMLTABLE, + TFT_JSON_TABLE +} TableFuncType; + /* * TableFunc - node for a table function, such as XMLTABLE. * @@ -82,6 +88,7 @@ typedef struct RangeVar typedef struct TableFunc { NodeTag type; + TableFuncType functype; /* XMLTABLE or JSON_TABLE */ List *ns_uris; /* list of namespace URI expressions */ List *ns_names; /* list of namespace names or NULL */ Node *docexpr; /* input document expression */ @@ -92,7 +99,9 @@ typedef struct TableFunc List *colcollations; /* OID list of column collation OIDs */ List *colexprs; /* list of column filter expressions */ List *coldefexprs; /* list of column default expressions */ + List *colvalexprs; /* list of column value expressions */ Bitmapset *notnulls; /* nullability flag for each output column */ + Node *plan; /* JSON_TABLE plan */ int ordinalitycol; /* counts from 0; -1 if none specified */ int location; /* token location, or -1 if unknown */ } TableFunc; @@ -1200,7 +1209,8 @@ typedef enum JsonExprOp { IS_JSON_VALUE, /* JSON_VALUE() */ IS_JSON_QUERY, /* JSON_QUERY() */ - IS_JSON_EXISTS /* JSON_EXISTS() */ + IS_JSON_EXISTS, /* JSON_EXISTS() */ + IS_JSON_TABLE /* JSON_TABLE() */ } JsonExprOp; /* @@ -1353,6 +1363,35 @@ typedef struct JsonExpr int location; /* token location, or -1 if unknown */ } JsonExpr; +/* + * JsonTableParentNode - + * transformed representation of parent JSON_TABLE plan node + */ +typedef struct JsonTableParentNode +{ + NodeTag type; + Const *path; /* jsonpath constant */ + char *name; /* path name */ + JsonPassing passing; /* PASSING arguments */ + Node *child; /* nested columns, if any */ + bool outerJoin; /* outer or inner join for nested columns? */ + int colMin; /* min column index in the resulting column list */ + int colMax; /* max column index in the resulting column list */ + bool errorOnError; /* ERROR/EMPTY ON ERROR behavior */ +} JsonTableParentNode; + +/* + * JsonTableSiblingNode - + * transformed representation of joined sibling JSON_TABLE plan node + */ +typedef struct JsonTableSiblingNode +{ + NodeTag type; + Node *larg; /* left join node */ + Node *rarg; /* right join node */ + bool cross; /* cross or union join? */ +} JsonTableSiblingNode; + /* ---------------- * NullTest * diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 51c1c7807a..860d23b238 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -232,6 +232,7 @@ PG_KEYWORD("json_exists", JSON_EXISTS, COL_NAME_KEYWORD) PG_KEYWORD("json_object", JSON_OBJECT, COL_NAME_KEYWORD) PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD) PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD) +PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD) PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD) PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) @@ -272,6 +273,7 @@ PG_KEYWORD("names", NAMES, UNRESERVED_KEYWORD) PG_KEYWORD("national", NATIONAL, COL_NAME_KEYWORD) PG_KEYWORD("natural", NATURAL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("nchar", NCHAR, COL_NAME_KEYWORD) +PG_KEYWORD("nested", NESTED, UNRESERVED_KEYWORD) PG_KEYWORD("new", NEW, UNRESERVED_KEYWORD) PG_KEYWORD("next", NEXT, UNRESERVED_KEYWORD) PG_KEYWORD("no", NO, UNRESERVED_KEYWORD) @@ -315,7 +317,9 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) +PG_KEYWORD("path", PATH, UNRESERVED_KEYWORD) PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) +PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD) PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD) PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD) PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD) diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index ecb8499e1e..b414c1f0dc 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -15,10 +15,12 @@ #define JSONPATH_H #include "fmgr.h" +#include "executor/tablefunc.h" #include "utils/jsonb.h" #include "utils/jsonapi.h" #include "nodes/pg_list.h" #include "nodes/primnodes.h" +#include "utils/jsonb.h" typedef struct { @@ -260,6 +262,7 @@ typedef struct JsonPathVariableEvalContext int32 typmod; struct ExprContext *econtext; struct ExprState *estate; + MemoryContext mcxt; /* memory context for cached value */ Datum value; bool isnull; bool evaluated; @@ -349,4 +352,6 @@ extern JsonItem *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); +extern const TableFuncRoutine JsonbTableRoutine; + #endif diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index 9fc56b0ff4..caaabff0d7 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1038,6 +1038,11 @@ INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1); ERROR: new row for relation "test_json_constraints" violates check constraint "test_json_constraint4" DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]). DROP TABLE test_json_constraints; +-- JSON_TABLE +SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text)); +ERROR: JSON_TABLE() is not yet implemented for json type +LINE 1: SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo ... + ^ -- Extension: non-constant JSON path SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); json_exists diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index d4c7b93beb..6a5423027d 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -915,6 +915,952 @@ 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; +-- JSON_TABLE +-- Should fail (JSON_TABLE can be used only in FROM clause) +SELECT JSON_TABLE('[]', '$'); +ERROR: syntax error at or near "(" +LINE 1: SELECT JSON_TABLE('[]', '$'); + ^ +-- Should fail (no columns) +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); +ERROR: syntax error at or near ")" +LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); + ^ +-- NULL => empty table +SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar; + foo +----- +(0 rows) + +-- +SELECT * FROM JSON_TABLE(jsonb '123', '$' + COLUMNS (item int PATH '$', foo int)) bar; + item | foo +------+----- + 123 | +(1 row) + +-- JSON_TABLE: basic functionality +SELECT * +FROM + (VALUES + ('1'), + ('[]'), + ('{}'), + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]') + ) vals(js) + LEFT OUTER JOIN +-- JSON_TABLE is implicitly lateral + JSON_TABLE( + vals.js::jsonb, 'lax $[*]' + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa' + ) + ) jt + ON true; + js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | aaa | aaa1 +--------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+-----------+-----------+--------------+------+------+--------------+-----+------ + 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | + [] | | | | | | | | | | | | | | | + {} | 1 | 1 | | | | | | | | {} | {} | {} | {} | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | | | | | | | null | null | null | null | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | 0 | false | fals | f | | false | false | false | fals | fals | false | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 1 | true | true | t | | true | true | true | true | true | true | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | | | | | | | | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | 123 | 123 + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | | +(13 rows) + +-- JSON_TABLE: Test backward parsing +CREATE VIEW jsonb_table_view AS +SELECT * FROM + JSON_TABLE( + jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa', + NESTED PATH '$[1]' AS p1 COLUMNS ( + a1 int, + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( + a11 text + ), + b1 text + ), + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( + a21 text + ), + NESTED PATH '$[*]' AS p22 COLUMNS ( + a22 text + ) + ) + ) + ); +\sv jsonb_table_view +CREATE OR REPLACE VIEW public.jsonb_table_view AS + SELECT "json_table".id, + "json_table".id2, + "json_table"."int", + "json_table".text, + "json_table"."char(4)", + "json_table".bool, + "json_table"."numeric", + "json_table".js, + "json_table".jb, + "json_table".jst, + "json_table".jsc, + "json_table".jsv, + "json_table".jsb, + "json_table".aaa, + "json_table".aaa1, + "json_table".a1, + "json_table".b1, + "json_table".a11, + "json_table".a21, + "json_table".a22 + FROM JSON_TABLE( + 'null'::jsonb, '$[*]' AS json_table_path_1 + PASSING + 1 + 2 AS a, + '"foo"'::json AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, + "int" integer PATH '$', + text text PATH '$', + "char(4)" character(4) PATH '$', + bool boolean PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc character(4) FORMAT JSON PATH '$', + jsv character varying(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa integer PATH '$."aaa"', + aaa1 integer PATH '$."aaa"', + NESTED PATH '$[1]' AS p1 + COLUMNS ( + a1 integer PATH '$."a1"', + b1 text PATH '$."b1"', + NESTED PATH '$[*]' AS "p1 1" + COLUMNS ( + a11 text PATH '$."a11"' + ) + ), + NESTED PATH '$[2]' AS p2 + COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" + COLUMNS ( + a21 text PATH '$."a21"' + ), + NESTED PATH '$[*]' AS p22 + COLUMNS ( + a22 text PATH '$."a22"' + ) + ) + ) + PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) + ) +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Table Function Scan on "json_table" + Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".aaa, "json_table".aaa1, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22 + Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))) +(3 rows) + +-- JSON_TABLE: ON EMPTY/ON ERROR behavior +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js), + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt; + js | a +-------+--- + 1 | 1 + "err" | +(2 rows) + +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + ON true; +ERROR: invalid input syntax for type integer: "err" +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + ON true; +ERROR: invalid input syntax for type integer: "err" +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; + a +--- + +(1 row) + +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +ERROR: jsonpath member accessor can only be applied to an object +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +ERROR: no SQL/JSON item +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 2 +(1 row) + +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 2 +(1 row) + +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 1 +(1 row) + +-- JSON_TABLE: nested paths and plans +-- Should fail (JSON_TABLE columns shall contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: jsonb '[]', '$' + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 4: NESTED PATH '$' COLUMNS ( + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +-- Should fail (column names anf path names shall be distinct) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a + COLUMNS ( + a int + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a + COLUMNS ( + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' + COLUMNS ( + b int, + NESTED PATH '$' AS b + COLUMNS ( + c int + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: b +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + b int + ), + NESTED PATH '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +-- JSON_TABLE: plan validation +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p1) + ^ +DETAIL: path name mismatch: expected p0 but p1 is given +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 UNION p1 UNION p11) + ^ +DETAIL: expected INNER or OUTER JSON_TABLE plan node +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 8: NESTED PATH '$' AS p2 COLUMNS ( + ^ +DETAIL: plan node for nested path p2 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ), + ^ +DETAIL: plan node for nested path p11 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) + ^ +DETAIL: plan node contains some extra or duplicate sibling nodes +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ^ +DETAIL: plan node for nested path p12 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ^ +DETAIL: plan node for nested path p21 was not found in plan +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + bar | foo | baz +-----+-----+----- +(0 rows) + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: jsonb 'null', 'strict $[*]' + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +-- JSON_TABLE: plan execution +CREATE TEMP TABLE jsonb_table_test (js jsonb); +INSERT INTO jsonb_table_test +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + n | a | c | b +---+----+----+--- + 1 | 1 | | + 2 | 2 | 10 | + 2 | 2 | | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 3 + 3 | 3 | | 1 + 3 | 3 | | 2 + 4 | -1 | | 1 + 4 | -1 | | 2 +(11 rows) + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + n | a | b | c +---+----+---+---- + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(10 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt; + n | a | b | c +---+----+---+---- + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(10 rows) + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + n | a | b | c +---+---+---+---- + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 +(9 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt; + n | a | b | c +---+---+---+---- + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 +(9 rows) + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; + n | a | b | b1 | c | c1 | b +---+---+--------------+-----+------+----+----- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 2 | 2 | | | | | + 3 | | | | | | +(20 rows) + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(jsonb + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt; + x | y | y | z +---+---+--------------+--- + 2 | 1 | [1, 2, 3] | 1 + 2 | 1 | [1, 2, 3] | 2 + 2 | 1 | [1, 2, 3] | 3 + 3 | 1 | [1, 2, 3] | 1 + 3 | 1 | [1, 2, 3] | 2 + 3 | 1 | [1, 2, 3] | 3 + 3 | 1 | [2, 3, 4, 5] | 2 + 3 | 1 | [2, 3, 4, 5] | 3 + 3 | 1 | [2, 3, 4, 5] | 4 + 3 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [1, 2, 3] | 1 + 4 | 1 | [1, 2, 3] | 2 + 4 | 1 | [1, 2, 3] | 3 + 4 | 1 | [2, 3, 4, 5] | 2 + 4 | 1 | [2, 3, 4, 5] | 3 + 4 | 1 | [2, 3, 4, 5] | 4 + 4 | 1 | [2, 3, 4, 5] | 5 + 4 | 1 | [3, 4, 5, 6] | 3 + 4 | 1 | [3, 4, 5, 6] | 4 + 4 | 1 | [3, 4, 5, 6] | 5 + 4 | 1 | [3, 4, 5, 6] | 6 + 2 | 2 | [1, 2, 3] | 2 + 2 | 2 | [1, 2, 3] | 3 + 3 | 2 | [1, 2, 3] | 2 + 3 | 2 | [1, 2, 3] | 3 + 3 | 2 | [2, 3, 4, 5] | 2 + 3 | 2 | [2, 3, 4, 5] | 3 + 3 | 2 | [2, 3, 4, 5] | 4 + 3 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [1, 2, 3] | 2 + 4 | 2 | [1, 2, 3] | 3 + 4 | 2 | [2, 3, 4, 5] | 2 + 4 | 2 | [2, 3, 4, 5] | 3 + 4 | 2 | [2, 3, 4, 5] | 4 + 4 | 2 | [2, 3, 4, 5] | 5 + 4 | 2 | [3, 4, 5, 6] | 3 + 4 | 2 | [3, 4, 5, 6] | 4 + 4 | 2 | [3, 4, 5, 6] | 5 + 4 | 2 | [3, 4, 5, 6] | 6 + 2 | 3 | [1, 2, 3] | 3 + 3 | 3 | [1, 2, 3] | 3 + 3 | 3 | [2, 3, 4, 5] | 3 + 3 | 3 | [2, 3, 4, 5] | 4 + 3 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [1, 2, 3] | 3 + 4 | 3 | [2, 3, 4, 5] | 3 + 4 | 3 | [2, 3, 4, 5] | 4 + 4 | 3 | [2, 3, 4, 5] | 5 + 4 | 3 | [3, 4, 5, 6] | 3 + 4 | 3 | [3, 4, 5, 6] | 4 + 4 | 3 | [3, 4, 5, 6] | 5 + 4 | 3 | [3, 4, 5, 6] | 6 +(52 rows) + +-- Should fail (JSON arguments are not passed to column paths) +SELECT * +FROM JSON_TABLE( + jsonb '[1,2,3]', + '$[*] ? (@ < $x)' + PASSING 10 AS x + COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)') + ) jt; +ERROR: could not find jsonpath variable "x" -- Extension: non-constant JSON path SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); json_exists diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 3a39f600cb..6fb0c6a255 100644 --- a/src/test/regress/sql/json_sqljson.sql +++ b/src/test/regress/sql/json_sqljson.sql @@ -302,6 +302,10 @@ INSERT INTO test_json_constraints VALUES ('{"a": 10}', 1); DROP TABLE test_json_constraints; +-- JSON_TABLE + +SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text)); + -- Extension: non-constant JSON path SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_VALUE(json '{"a": 123}', '$' || '.' || 'a'); diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 8bb9e016c7..c49e1b851e 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -277,6 +277,582 @@ INSERT INTO test_jsonb_constraints VALUES ('{"a": 10}', 1); DROP TABLE test_jsonb_constraints; +-- JSON_TABLE + +-- Should fail (JSON_TABLE can be used only in FROM clause) +SELECT JSON_TABLE('[]', '$'); + +-- Should fail (no columns) +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); + +-- NULL => empty table +SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar; + +-- +SELECT * FROM JSON_TABLE(jsonb '123', '$' + COLUMNS (item int PATH '$', foo int)) bar; + +-- JSON_TABLE: basic functionality +SELECT * +FROM + (VALUES + ('1'), + ('[]'), + ('{}'), + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]') + ) vals(js) + LEFT OUTER JOIN +-- JSON_TABLE is implicitly lateral + JSON_TABLE( + vals.js::jsonb, 'lax $[*]' + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa' + ) + ) jt + ON true; + +-- JSON_TABLE: Test backward parsing + +CREATE VIEW jsonb_table_view AS +SELECT * FROM + JSON_TABLE( + jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa', + NESTED PATH '$[1]' AS p1 COLUMNS ( + a1 int, + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( + a11 text + ), + b1 text + ), + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( + a21 text + ), + NESTED PATH '$[*]' AS p22 COLUMNS ( + a22 text + ) + ) + ) + ); + +\sv jsonb_table_view + +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view; + +-- JSON_TABLE: ON EMPTY/ON ERROR behavior +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js), + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt; + +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + ON true; + +SELECT * +FROM + (VALUES ('1'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + ON true; + +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; + +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; +SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + +-- JSON_TABLE: nested paths and plans + +-- Should fail (JSON_TABLE columns shall contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; + +-- Should fail (column names anf path names shall be distinct) +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a + COLUMNS ( + a int + ) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' AS a + COLUMNS ( + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' + COLUMNS ( + b int, + NESTED PATH '$' AS b + COLUMNS ( + c int + ) + ) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb '[]', '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + b int + ), + NESTED PATH '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) + ) +) jt; + +-- JSON_TABLE: plan validation + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + +SELECT * FROM JSON_TABLE( + jsonb 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; + +-- JSON_TABLE: plan execution + +CREATE TEMP TABLE jsonb_table_test (js jsonb); + +INSERT INTO jsonb_table_test +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); + +-- unspecified plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt; + +-- default plan (outer, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt; + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + +-- default plan (inner, union) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt; + +-- default plan (inner, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt; + +-- default plan (outer, cross) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + jsonb_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt; + + +select + jt.*, b1 + 100 as b +from + json_table (jsonb + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(jsonb + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt; + +-- Should fail (JSON arguments are not passed to column paths) +SELECT * +FROM JSON_TABLE( + jsonb '[1,2,3]', + '$[*] ? (@ < $x)' + PASSING 10 AS x + COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)') + ) jt; + -- Extension: non-constant JSON path SELECT JSON_EXISTS(jsonb '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a'); From 1696acc5742b41093b6ccd1889068ef1c60481a4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 7 Dec 2017 19:19:51 +0300 Subject: [PATCH 41/66] Allow variable jsonpath specifications in JSON_TABLE --- src/backend/parser/parse_clause.c | 35 ++++++++++++++++++--- src/backend/utils/adt/ruleutils.c | 3 +- src/test/regress/expected/jsonb_sqljson.out | 5 +++ src/test/regress/sql/jsonb_sqljson.sql | 3 +- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 6e7d1d1f92..c2671f0f6d 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1029,6 +1029,18 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts) return tablesample; } +static Node * +makeStringConst(char *str, int location) +{ + A_Const *n = makeNode(A_Const); + + n->val.type = T_String; + n->val.val.str = str; + n->location = location; + + return (Node *)n; +} + /* * getRTEForSpecialRelationTypes * @@ -1072,6 +1084,7 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr, JsonValueExpr *jvexpr = makeNode(JsonValueExpr); JsonCommon *common = makeNode(JsonCommon); JsonOutput *output = makeNode(JsonOutput); + JsonPathSpec pathspec; jfexpr->op = jtc->coltype == JTC_REGULAR ? IS_JSON_VALUE : IS_JSON_QUERY; jfexpr->common = common; @@ -1092,7 +1105,7 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr, common->passing = passingArgs; if (jtc->pathspec) - common->pathspec = jtc->pathspec; + pathspec = jtc->pathspec; else { /* Construct default path as '$."column_name"' */ @@ -1103,9 +1116,11 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr, appendStringInfoString(&path, "$."); escape_json(&path, jtc->name); - common->pathspec = path.data; + pathspec = path.data; } + common->pathspec = makeStringConst(pathspec, -1); + jvexpr->expr = (Expr *) contextItemExpr; jvexpr->format.type = JS_FORMAT_DEFAULT; jvexpr->format.encoding = JS_ENC_DEFAULT; @@ -1606,6 +1621,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) JsonCommon *jscommon; JsonTablePlan *plan = jt->plan; char *rootPathName = jt->common->pathname; + char *rootPath; bool is_lateral; cxt.table = jt; @@ -1637,7 +1653,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) #endif jscommon = copyObject(jt->common); - jscommon->pathspec = pstrdup("$"); + jscommon->pathspec = makeStringConst(pstrdup("$"), -1); jfe->op = IS_JSON_TABLE; jfe->common = jscommon; @@ -1661,10 +1677,19 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) cxt.contextItemTypid = exprType(tf->docexpr); + if (!IsA(jt->common->pathspec, A_Const) || + castNode(A_Const, jt->common->pathspec)->val.type != T_String) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only string constants supported in JSON_TABLE path specification"), + parser_errposition(pstate, + exprLocation(jt->common->pathspec)))); + + rootPath = castNode(A_Const, jt->common->pathspec)->val.val.str; + tf->plan = (Node *) transformJsonTableColumns(pstate, &cxt, plan, jt->columns, - jt->common->pathspec, - &rootPathName, + rootPath, &rootPathName, jt->common->location); tf->ordinalitycol = -1; /* undefine ordinality column number */ diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index e7c15003c4..f73c60ca88 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10360,7 +10360,8 @@ get_json_table_columns(TableFunc *tf, JsonTableParentNode *node, " FORMAT JSONB" : " FORMAT JSON"); appendStringInfoString(buf, " PATH "); - get_const_expr(colexpr->path_spec, context, -1); + + get_json_path_spec(colexpr->path_spec, context, showimplicit); if (colexpr->wrapper == JSW_CONDITIONAL) appendStringInfo(buf, " WITH CONDITIONAL WRAPPER"); diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index 6a5423027d..3bfa6e2825 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -1895,6 +1895,11 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input +-- Should fail (not supported) +SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); +ERROR: only string constants supported in JSON_TABLE path specification +LINE 1: SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || '... + ^ -- Test parallel JSON_VALUE() CREATE TABLE test_parallel_jsonb_value AS SELECT i::text::jsonb AS js diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index c49e1b851e..bae79cb1c7 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -861,6 +861,8 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); +-- Should fail (not supported) +SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); -- Test parallel JSON_VALUE() CREATE TABLE test_parallel_jsonb_value AS @@ -876,4 +878,3 @@ SELECT sum(JSON_VALUE(js, '$' RETURNING numeric)) FROM test_parallel_jsonb_value EXPLAIN (COSTS OFF) SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; SELECT sum(JSON_VALUE(js, '$' RETURNING numeric ERROR ON ERROR)) FROM test_parallel_jsonb_value; - From 13410cc75ec55f4be97a21fa7fc1c56a78c0d15a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:05:11 +0300 Subject: [PATCH 42/66] Add json support for JSON_TABLE --- src/backend/executor/nodeTableFuncscan.c | 5 +- src/backend/parser/parse_expr.c | 6 - src/backend/utils/adt/jsonpath_exec.c | 64 +- src/include/utils/jsonpath.h | 1 + src/test/regress/expected/json_sqljson.out | 981 ++++++++++++++++++++- src/test/regress/sql/json_sqljson.sql | 587 +++++++++++- 6 files changed, 1613 insertions(+), 31 deletions(-) diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index f76c285ddc..98adecdcf4 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -23,10 +23,12 @@ #include "postgres.h" #include "nodes/execnodes.h" +#include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/nodeTableFuncscan.h" #include "executor/tablefunc.h" #include "miscadmin.h" +#include "nodes/nodeFuncs.h" #include "utils/builtins.h" #include "utils/jsonpath.h" #include "utils/lsyscache.h" @@ -165,7 +167,8 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) /* Only XMLTABLE and JSON_TABLE are supported currently */ scanstate->routine = - tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : &JsonbTableRoutine; + tf->functype == TFT_XMLTABLE ? &XmlTableRoutine : + exprType(tf->docexpr) == JSONBOID ? &JsonbTableRoutine : &JsonTableRoutine; scanstate->perTableCxt = AllocSetContextCreate(CurrentMemoryContext, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index b7f7d58f1a..421973bb9a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4824,12 +4824,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) jsexpr->returning.typid = exprType(contextItemExpr); jsexpr->returning.typmod = -1; - if (jsexpr->returning.typid != JSONBOID) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("JSON_TABLE() is not yet implemented for json type"), - parser_errposition(pstate, func->location))); - break; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 34ab5c82ff..b68ab562a5 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -261,6 +261,7 @@ typedef struct JsonTableContext } *colexprs; JsonTableScanState root; bool empty; + bool isJsonb; } JsonTableContext; /* strict/lax flags is decomposed into four [un]wrap/error flags */ @@ -434,7 +435,7 @@ static void popJsonItem(JsonItemStack *stack); static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, Node *plan, JsonTableScanState *parent); -static bool JsonTableNextRow(JsonTableScanState *scan); +static bool JsonTableNextRow(JsonTableScanState *scan, bool isJsonb); /****************** User interface to JsonPath executor ********************/ @@ -3748,11 +3749,11 @@ JsonTableInitPlanState(JsonTableContext *cxt, Node *plan, } /* - * JsonTableInitOpaque + * JsonxTableInitOpaque * Fill in TableFuncScanState->opaque for JsonTable processor */ static void -JsonTableInitOpaque(TableFuncScanState *state, int natts) +JsonxTableInitOpaque(TableFuncScanState *state, int natts, bool isJsonb) { JsonTableContext *cxt; PlanState *ps = &state->ss.ps; @@ -3766,6 +3767,7 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts) cxt = palloc0(sizeof(JsonTableContext)); cxt->magic = JSON_TABLE_CONTEXT_MAGIC; + cxt->isJsonb = isJsonb; if (list_length(ci->passing.values) > 0) { @@ -3816,6 +3818,18 @@ JsonTableInitOpaque(TableFuncScanState *state, int natts) state->opaque = cxt; } +static void +JsonbTableInitOpaque(TableFuncScanState *state, int natts) +{ + JsonxTableInitOpaque(state, natts, true); +} + +static void +JsonTableInitOpaque(TableFuncScanState *state, int natts) +{ + JsonxTableInitOpaque(state, natts, false); +} + /* Reset scan iterator to the beginning of the item list */ static void JsonTableRescan(JsonTableScanState *scan) @@ -3829,11 +3843,11 @@ JsonTableRescan(JsonTableScanState *scan) /* Reset context item of a scan, execute JSON path and reset a scan */ static void -JsonTableResetContextItem(JsonTableScanState *scan, Datum item) +JsonTableResetContextItem(JsonTableScanState *scan, Datum item, bool isJsonb) { MemoryContext oldcxt; JsonPathExecResult res; - Jsonx *js = (Jsonx *) DatumGetJsonbP(item); + Jsonx *js = DatumGetJsonxP(item, isJsonb); JsonValueListClear(&scan->found); @@ -3841,7 +3855,7 @@ JsonTableResetContextItem(JsonTableScanState *scan, Datum item) oldcxt = MemoryContextSwitchTo(scan->mcxt); - res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js, true, + res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js, isJsonb, scan->errorOnError, &scan->found); MemoryContextSwitchTo(oldcxt); @@ -3864,7 +3878,7 @@ JsonTableSetDocument(TableFuncScanState *state, Datum value) { JsonTableContext *cxt = GetJsonTableContext(state, "JsonTableSetDocument"); - JsonTableResetContextItem(&cxt->root, value); + JsonTableResetContextItem(&cxt->root, value, cxt->isJsonb); } /* Recursively reset scan and its child nodes */ @@ -3891,15 +3905,15 @@ JsonTableRescanRecursive(JsonTableJoinState *state) * Returned false at the end of a scan, true otherwise. */ static bool -JsonTableNextJoinRow(JsonTableJoinState *state) +JsonTableNextJoinRow(JsonTableJoinState *state, bool isJsonb) { if (!state->is_join) - return JsonTableNextRow(&state->u.scan); + return JsonTableNextRow(&state->u.scan, isJsonb); if (state->u.join.advanceRight) { /* fetch next inner row */ - if (JsonTableNextJoinRow(state->u.join.right)) + if (JsonTableNextJoinRow(state->u.join.right, isJsonb)) return true; /* inner rows are exhausted */ @@ -3912,7 +3926,7 @@ JsonTableNextJoinRow(JsonTableJoinState *state) while (!state->u.join.advanceRight) { /* fetch next outer row */ - bool left = JsonTableNextJoinRow(state->u.join.left); + bool left = JsonTableNextJoinRow(state->u.join.left, isJsonb); if (state->u.join.cross) { @@ -3921,14 +3935,14 @@ JsonTableNextJoinRow(JsonTableJoinState *state) JsonTableRescanRecursive(state->u.join.right); - if (!JsonTableNextJoinRow(state->u.join.right)) + if (!JsonTableNextJoinRow(state->u.join.right, isJsonb)) continue; /* next outer row */ state->u.join.advanceRight = true; /* next inner row */ } else if (!left) { - if (!JsonTableNextJoinRow(state->u.join.right)) + if (!JsonTableNextJoinRow(state->u.join.right, isJsonb)) return false; /* end of scan */ state->u.join.advanceRight = true; /* next inner row */ @@ -3966,20 +3980,20 @@ JsonTableJoinReset(JsonTableJoinState *state) * Returned false at the end of a scan, true otherwise. */ static bool -JsonTableNextRow(JsonTableScanState *scan) +JsonTableNextRow(JsonTableScanState *scan, bool isJsonb) { /* reset context item if requested */ if (scan->reset) { Assert(!scan->parent->currentIsNull); - JsonTableResetContextItem(scan, scan->parent->current); + JsonTableResetContextItem(scan, scan->parent->current, isJsonb); scan->reset = false; } if (scan->advanceNested) { /* fetch next nested row */ - scan->advanceNested = JsonTableNextJoinRow(scan->nested); + scan->advanceNested = JsonTableNextJoinRow(scan->nested, isJsonb); if (scan->advanceNested) return true; @@ -4000,7 +4014,7 @@ JsonTableNextRow(JsonTableScanState *scan) /* set current row item */ oldcxt = MemoryContextSwitchTo(scan->mcxt); - scan->current = JsonbPGetDatum(JsonItemToJsonb(jbv)); + scan->current = JsonItemToJsonxDatum(jbv, isJsonb); scan->currentIsNull = false; MemoryContextSwitchTo(oldcxt); @@ -4011,7 +4025,7 @@ JsonTableNextRow(JsonTableScanState *scan) JsonTableJoinReset(scan->nested); - scan->advanceNested = JsonTableNextJoinRow(scan->nested); + scan->advanceNested = JsonTableNextJoinRow(scan->nested, isJsonb); if (scan->advanceNested || scan->outerJoin) break; @@ -4035,7 +4049,7 @@ JsonTableFetchRow(TableFuncScanState *state) if (cxt->empty) return false; - return JsonTableNextRow(&cxt->root); + return JsonTableNextRow(&cxt->root, cxt->isJsonb); } /* @@ -4088,6 +4102,18 @@ JsonTableDestroyOpaque(TableFuncScanState *state) } const TableFuncRoutine JsonbTableRoutine = +{ + JsonbTableInitOpaque, + JsonTableSetDocument, + NULL, + NULL, + NULL, + JsonTableFetchRow, + JsonTableGetValue, + JsonTableDestroyOpaque +}; + +const TableFuncRoutine JsonTableRoutine = { JsonTableInitOpaque, JsonTableSetDocument, diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index b414c1f0dc..28717a6a41 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -352,6 +352,7 @@ extern JsonItem *JsonPathValue(Datum jb, JsonPath *jp, bool *empty, extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); +extern const TableFuncRoutine JsonTableRoutine; extern const TableFuncRoutine JsonbTableRoutine; #endif diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index caaabff0d7..ef854a673f 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1039,10 +1039,978 @@ ERROR: new row for relation "test_json_constraints" violates check constraint " DETAIL: Failing row contains ({"a": 10}, 1, [1, 2]). DROP TABLE test_json_constraints; -- JSON_TABLE -SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text)); -ERROR: JSON_TABLE() is not yet implemented for json type -LINE 1: SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo ... - ^ +-- Should fail (JSON_TABLE can be used only in FROM clause) +SELECT JSON_TABLE('[]', '$'); +ERROR: syntax error at or near "(" +LINE 1: SELECT JSON_TABLE('[]', '$'); + ^ +-- Should fail (no columns) +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); +ERROR: syntax error at or near ")" +LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); + ^ +-- NULL => empty table +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS (foo int)) bar; + foo +----- +(0 rows) + +-- invalid json => empty table +SELECT * FROM JSON_TABLE('', '$' COLUMNS (foo int)) bar; + foo +----- +(0 rows) + +SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int)) bar; + foo +----- +(0 rows) + +-- invalid json => error +SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int) ERROR ON ERROR) bar; +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +-- +SELECT * FROM JSON_TABLE('123' FORMAT JSON, '$' + COLUMNS (item int PATH '$', foo int)) bar; + item | foo +------+----- + 123 | +(1 row) + +SELECT * FROM JSON_TABLE(json '123', '$' + COLUMNS (item int PATH '$', foo int)) bar; + item | foo +------+----- + 123 | +(1 row) + +-- JSON_TABLE: basic functionality +SELECT * +FROM + (VALUES + ('1'), + ('[]'), + ('{}'), + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'), + ('err') + ) vals(js) + LEFT OUTER JOIN +-- JSON_TABLE is implicitly lateral + JSON_TABLE( + vals.js FORMAT json, 'lax $[*]' + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa' + ) + ) jt + ON true; + js | id | id2 | int | text | char(4) | bool | numeric | js | jb | jst | jsc | jsv | jsb | aaa | aaa1 +--------------------------------------------------------------------------------+----+-----+-----+---------+---------+------+---------+-----------+-----------+--------------+------+------+--------------+-----+------ + 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | + [] | | | | | | | | | | | | | | | + {} | 1 | 1 | | | | | | | | {} | {} | {} | {} | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 2 | 2 | 1 | 1.23 | 1.23 | | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | 1.23 | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 3 | 3 | 2 | 2 | 2 | | 2 | "2" | "2" | "2" | "2" | "2" | "2" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 4 | 4 | | aaaaaaa | aaaa | | | "aaaaaaa" | "aaaaaaa" | "aaaaaaa" | "aaa | "aaa | "aaaaaaa" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 5 | 5 | | | | | | | | null | null | null | null | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 6 | 6 | 0 | false | fals | f | | false | false | false | fals | fals | false | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 7 | 7 | 1 | true | true | t | | true | true | true | true | true | true | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | | | | | | | | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | 123 | 123 + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | | + [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | | + err | | | | | | | | | | | | | | | +(14 rows) + +-- JSON_TABLE: Test backward parsing +CREATE VIEW json_table_view AS +SELECT * FROM + JSON_TABLE( + 'null' FORMAT JSON, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa', + NESTED PATH '$[1]' AS p1 COLUMNS ( + a1 int, + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( + a11 text + ), + b1 text + ), + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( + a21 text + ), + NESTED PATH '$[*]' AS p22 COLUMNS ( + a22 text + ) + ) + ) + ); +\sv json_table_view +CREATE OR REPLACE VIEW public.json_table_view AS + SELECT "json_table".id, + "json_table".id2, + "json_table"."int", + "json_table".text, + "json_table"."char(4)", + "json_table".bool, + "json_table"."numeric", + "json_table".js, + "json_table".jb, + "json_table".jst, + "json_table".jsc, + "json_table".jsv, + "json_table".jsb, + "json_table".aaa, + "json_table".aaa1, + "json_table".a1, + "json_table".b1, + "json_table".a11, + "json_table".a21, + "json_table".a22 + FROM JSON_TABLE( + 'null'::text FORMAT JSON, '$[*]' AS json_table_path_1 + PASSING + 1 + 2 AS a, + '"foo"'::json AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, + "int" integer PATH '$', + text text PATH '$', + "char(4)" character(4) PATH '$', + bool boolean PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc character(4) FORMAT JSON PATH '$', + jsv character varying(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa integer PATH '$."aaa"', + aaa1 integer PATH '$."aaa"', + NESTED PATH '$[1]' AS p1 + COLUMNS ( + a1 integer PATH '$."a1"', + b1 text PATH '$."b1"', + NESTED PATH '$[*]' AS "p1 1" + COLUMNS ( + a11 text PATH '$."a11"' + ) + ), + NESTED PATH '$[2]' AS p2 + COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" + COLUMNS ( + a21 text PATH '$."a21"' + ), + NESTED PATH '$[*]' AS p22 + COLUMNS ( + a22 text PATH '$."a22"' + ) + ) + ) + PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) + ) +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM json_table_view; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Table Function Scan on "json_table" + Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".aaa, "json_table".aaa1, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22 + Table Function Call: JSON_TABLE('null'::text FORMAT JSON, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::json AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))) +(3 rows) + +-- JSON_TABLE: ON EMPTY/ON ERROR behavior +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js), + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$')) jt; + js | a +-------+--- + 1 | 1 + "err" | +(2 rows) + +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + ON true; +ERROR: invalid input syntax for type json +DETAIL: Token "err" is invalid. +CONTEXT: JSON data, line 1: err +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + ON true; +ERROR: invalid input syntax for type integer: "err" +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; + a +--- + +(1 row) + +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +ERROR: jsonpath member accessor can only be applied to an object +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +ERROR: no SQL/JSON item +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 2 +(1 row) + +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 2 +(1 row) + +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + a +--- + 1 +(1 row) + +-- JSON_TABLE: nested paths and plans +-- Should fail (JSON_TABLE columns shall contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + json '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: json '[]', '$' + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +SELECT * FROM JSON_TABLE( + json '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 4: NESTED PATH '$' COLUMNS ( + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +-- Should fail (column names anf path names shall be distinct) +SELECT * FROM JSON_TABLE( + json '[]', '$' AS a + COLUMNS ( + a int + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + json '[]', '$' AS a + COLUMNS ( + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + json '[]', '$' + COLUMNS ( + b int, + NESTED PATH '$' AS b + COLUMNS ( + c int + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: b +HINT: JSON_TABLE path names and column names shall be distinct from one another +SELECT * FROM JSON_TABLE( + json '[]', '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + b int + ), + NESTED PATH '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) + ) +) jt; +ERROR: duplicate JSON_TABLE column name: a +HINT: JSON_TABLE path names and column names shall be distinct from one another +-- JSON_TABLE: plan validation +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p1) + ^ +DETAIL: path name mismatch: expected p0 but p1 is given +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 4: NESTED PATH '$' AS p1 COLUMNS ( + ^ +DETAIL: plan node for nested path p1 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 UNION p1 UNION p11) + ^ +DETAIL: expected INNER or OUTER JSON_TABLE plan node +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 8: NESTED PATH '$' AS p2 COLUMNS ( + ^ +DETAIL: plan node for nested path p2 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 5: NESTED PATH '$' AS p11 COLUMNS ( foo int ), + ^ +DETAIL: plan node for nested path p11 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 12: PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) + ^ +DETAIL: plan node contains some extra or duplicate sibling nodes +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 6: NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ^ +DETAIL: plan node for nested path p12 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; +ERROR: invalid JSON_TABLE plan +LINE 9: NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ^ +DETAIL: plan node for nested path p21 was not found in plan +SELECT * FROM JSON_TABLE( + json 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + bar | foo | baz +-----+-----+----- +(0 rows) + +SELECT * FROM JSON_TABLE( + json 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; +ERROR: invalid JSON_TABLE expression +LINE 2: json 'null', 'strict $[*]' + ^ +DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used +-- JSON_TABLE: plan execution +CREATE TEMP TABLE json_table_test (js text); +INSERT INTO json_table_test +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); +-- unspecified plan (outer, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- default plan (outer, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(11 rows) + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + n | a | c | b +---+----+----+--- + 1 | 1 | | + 2 | 2 | 10 | + 2 | 2 | | + 2 | 2 | 20 | + 2 | 2 | | 1 + 2 | 2 | | 2 + 2 | 2 | | 3 + 3 | 3 | | 1 + 3 | 3 | | 2 + 4 | -1 | | 1 + 4 | -1 | | 2 +(11 rows) + +-- default plan (inner, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + n | a | b | c +---+----+---+---- + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(10 rows) + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt; + n | a | b | c +---+----+---+---- + 2 | 2 | 1 | + 2 | 2 | 2 | + 2 | 2 | 3 | + 2 | 2 | | 10 + 2 | 2 | | + 2 | 2 | | 20 + 3 | 3 | 1 | + 3 | 3 | 2 | + 4 | -1 | 1 | + 4 | -1 | 2 | +(10 rows) + +-- default plan (inner, cross) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + n | a | b | c +---+---+---+---- + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 +(9 rows) + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt; + n | a | b | c +---+---+---+---- + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 +(9 rows) + +-- default plan (outer, cross) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt; + n | a | b | c +---+----+---+---- + 1 | 1 | | + 2 | 2 | 1 | 10 + 2 | 2 | 1 | + 2 | 2 | 1 | 20 + 2 | 2 | 2 | 10 + 2 | 2 | 2 | + 2 | 2 | 2 | 20 + 2 | 2 | 3 | 10 + 2 | 2 | 3 | + 2 | 2 | 3 | 20 + 3 | 3 | | + 4 | -1 | | +(12 rows) + +select + jt.*, b1 + 100 as b +from + json_table (json + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; + n | a | b | b1 | c | c1 | b +---+---+--------------+-----+------+----+----- + 1 | 1 | [1, 10] | 1 | 1 | | 101 + 1 | 1 | [1, 10] | 1 | null | | 101 + 1 | 1 | [1, 10] | 1 | 2 | | 101 + 1 | 1 | [1, 10] | 10 | 1 | | 110 + 1 | 1 | [1, 10] | 10 | null | | 110 + 1 | 1 | [1, 10] | 10 | 2 | | 110 + 1 | 1 | [2] | 2 | 1 | | 102 + 1 | 1 | [2] | 2 | null | | 102 + 1 | 1 | [2] | 2 | 2 | | 102 + 1 | 1 | [3, 30, 300] | 3 | 1 | | 103 + 1 | 1 | [3, 30, 300] | 3 | null | | 103 + 1 | 1 | [3, 30, 300] | 3 | 2 | | 103 + 1 | 1 | [3, 30, 300] | 30 | 1 | | 130 + 1 | 1 | [3, 30, 300] | 30 | null | | 130 + 1 | 1 | [3, 30, 300] | 30 | 2 | | 130 + 1 | 1 | [3, 30, 300] | 300 | 1 | | 400 + 1 | 1 | [3, 30, 300] | 300 | null | | 400 + 1 | 1 | [3, 30, 300] | 300 | 2 | | 400 + 2 | 2 | | | | | + 3 | | | | | | +(20 rows) + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(json + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt; + x | y | y | z +---+---+-----------+--- + 2 | 1 | [1,2,3] | 1 + 2 | 1 | [1,2,3] | 2 + 2 | 1 | [1,2,3] | 3 + 3 | 1 | [1,2,3] | 1 + 3 | 1 | [1,2,3] | 2 + 3 | 1 | [1,2,3] | 3 + 3 | 1 | [2,3,4,5] | 2 + 3 | 1 | [2,3,4,5] | 3 + 3 | 1 | [2,3,4,5] | 4 + 3 | 1 | [2,3,4,5] | 5 + 4 | 1 | [1,2,3] | 1 + 4 | 1 | [1,2,3] | 2 + 4 | 1 | [1,2,3] | 3 + 4 | 1 | [2,3,4,5] | 2 + 4 | 1 | [2,3,4,5] | 3 + 4 | 1 | [2,3,4,5] | 4 + 4 | 1 | [2,3,4,5] | 5 + 4 | 1 | [3,4,5,6] | 3 + 4 | 1 | [3,4,5,6] | 4 + 4 | 1 | [3,4,5,6] | 5 + 4 | 1 | [3,4,5,6] | 6 + 2 | 2 | [1,2,3] | 2 + 2 | 2 | [1,2,3] | 3 + 3 | 2 | [1,2,3] | 2 + 3 | 2 | [1,2,3] | 3 + 3 | 2 | [2,3,4,5] | 2 + 3 | 2 | [2,3,4,5] | 3 + 3 | 2 | [2,3,4,5] | 4 + 3 | 2 | [2,3,4,5] | 5 + 4 | 2 | [1,2,3] | 2 + 4 | 2 | [1,2,3] | 3 + 4 | 2 | [2,3,4,5] | 2 + 4 | 2 | [2,3,4,5] | 3 + 4 | 2 | [2,3,4,5] | 4 + 4 | 2 | [2,3,4,5] | 5 + 4 | 2 | [3,4,5,6] | 3 + 4 | 2 | [3,4,5,6] | 4 + 4 | 2 | [3,4,5,6] | 5 + 4 | 2 | [3,4,5,6] | 6 + 2 | 3 | [1,2,3] | 3 + 3 | 3 | [1,2,3] | 3 + 3 | 3 | [2,3,4,5] | 3 + 3 | 3 | [2,3,4,5] | 4 + 3 | 3 | [2,3,4,5] | 5 + 4 | 3 | [1,2,3] | 3 + 4 | 3 | [2,3,4,5] | 3 + 4 | 3 | [2,3,4,5] | 4 + 4 | 3 | [2,3,4,5] | 5 + 4 | 3 | [3,4,5,6] | 3 + 4 | 3 | [3,4,5,6] | 4 + 4 | 3 | [3,4,5,6] | 5 + 4 | 3 | [3,4,5,6] | 6 +(52 rows) + +-- Should fail (JSON arguments are not passed to column paths) +SELECT * +FROM JSON_TABLE( + json '[1,2,3]', + '$[*] ? (@ < $x)' + PASSING 10 AS x + COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)') + ) jt; +ERROR: could not find jsonpath variable "x" -- Extension: non-constant JSON path SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); json_exists @@ -1077,3 +2045,8 @@ SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input +-- Should fail (not supported) +SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); +ERROR: only string constants supported in JSON_TABLE path specification +LINE 1: SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a... + ^ diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 6fb0c6a255..8508957c8f 100644 --- a/src/test/regress/sql/json_sqljson.sql +++ b/src/test/regress/sql/json_sqljson.sql @@ -304,7 +304,590 @@ DROP TABLE test_json_constraints; -- JSON_TABLE -SELECT * FROM JSON_TABLE(NULL FORMAT JSON, '$' COLUMNS (foo text)); +-- Should fail (JSON_TABLE can be used only in FROM clause) +SELECT JSON_TABLE('[]', '$'); + +-- Should fail (no columns) +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); + +-- NULL => empty table +SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS (foo int)) bar; + +-- invalid json => empty table +SELECT * FROM JSON_TABLE('', '$' COLUMNS (foo int)) bar; +SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int)) bar; + +-- invalid json => error +SELECT * FROM JSON_TABLE('' FORMAT JSON, '$' COLUMNS (foo int) ERROR ON ERROR) bar; + +-- +SELECT * FROM JSON_TABLE('123' FORMAT JSON, '$' + COLUMNS (item int PATH '$', foo int)) bar; + +SELECT * FROM JSON_TABLE(json '123', '$' + COLUMNS (item int PATH '$', foo int)) bar; + +-- JSON_TABLE: basic functionality +SELECT * +FROM + (VALUES + ('1'), + ('[]'), + ('{}'), + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'), + ('err') + ) vals(js) + LEFT OUTER JOIN +-- JSON_TABLE is implicitly lateral + JSON_TABLE( + vals.js FORMAT json, 'lax $[*]' + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa' + ) + ) jt + ON true; + +-- JSON_TABLE: Test backward parsing + +CREATE VIEW json_table_view AS +SELECT * FROM + JSON_TABLE( + 'null' FORMAT JSON, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + COLUMNS ( + id FOR ORDINALITY, + id2 FOR ORDINALITY, -- allowed additional ordinality columns + "int" int PATH '$', + "text" text PATH '$', + "char(4)" char(4) PATH '$', + "bool" bool PATH '$', + "numeric" numeric PATH '$', + js json PATH '$', + jb jsonb PATH '$', + jst text FORMAT JSON PATH '$', + jsc char(4) FORMAT JSON PATH '$', + jsv varchar(4) FORMAT JSON PATH '$', + jsb jsonb FORMAT JSON PATH '$', + aaa int, -- implicit path '$."aaa"', + aaa1 int PATH '$.aaa', + NESTED PATH '$[1]' AS p1 COLUMNS ( + a1 int, + NESTED PATH '$[*]' AS "p1 1" COLUMNS ( + a11 text + ), + b1 text + ), + NESTED PATH '$[2]' AS p2 COLUMNS ( + NESTED PATH '$[*]' AS "p2:1" COLUMNS ( + a21 text + ), + NESTED PATH '$[*]' AS p22 COLUMNS ( + a22 text + ) + ) + ) + ); + +\sv json_table_view + +EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM json_table_view; + +-- JSON_TABLE: ON EMPTY/ON ERROR behavior +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js), + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$')) jt; + +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + ON true; + +SELECT * +FROM + (VALUES ('1'), ('err'), ('"err"')) vals(js) + LEFT OUTER JOIN + JSON_TABLE(vals.js FORMAT JSON, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + ON true; + +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; +SELECT * FROM JSON_TABLE('1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt; + +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; +SELECT * FROM JSON_TABLE(json '"a"', '$' COLUMNS (a int PATH 'lax $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt; + +-- JSON_TABLE: nested paths and plans + +-- Should fail (JSON_TABLE columns shall contain explicit AS path +-- specifications if explicit PLAN clause is used) +SELECT * FROM JSON_TABLE( + json '[]', '$' -- AS required here + COLUMNS ( + foo int PATH '$' + ) + PLAN DEFAULT (UNION) +) jt; + +SELECT * FROM JSON_TABLE( + json '[]', '$' AS path1 + COLUMNS ( + NESTED PATH '$' COLUMNS ( -- AS required here + foo int PATH '$' + ) + ) + PLAN DEFAULT (UNION) +) jt; + +-- Should fail (column names anf path names shall be distinct) +SELECT * FROM JSON_TABLE( + json '[]', '$' AS a + COLUMNS ( + a int + ) +) jt; + +SELECT * FROM JSON_TABLE( + json '[]', '$' AS a + COLUMNS ( + b int, + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) +) jt; + +SELECT * FROM JSON_TABLE( + json '[]', '$' + COLUMNS ( + b int, + NESTED PATH '$' AS b + COLUMNS ( + c int + ) + ) +) jt; + +SELECT * FROM JSON_TABLE( + json '[]', '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + b int + ), + NESTED PATH '$' + COLUMNS ( + NESTED PATH '$' AS a + COLUMNS ( + c int + ) + ) + ) +) jt; + +-- JSON_TABLE: plan validation + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p1) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER p3) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 UNION p1 UNION p11) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p13)) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER (p1 CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 UNION p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER p11) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', '$[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS p2)) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', 'strict $[*]' AS p0 + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN (p0 OUTER ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))) +) jt; + +SELECT * FROM JSON_TABLE( + json 'null', 'strict $[*]' -- without root path name + COLUMNS ( + NESTED PATH '$' AS p1 COLUMNS ( + NESTED PATH '$' AS p11 COLUMNS ( foo int ), + NESTED PATH '$' AS p12 COLUMNS ( bar int ) + ), + NESTED PATH '$' AS p2 COLUMNS ( + NESTED PATH '$' AS p21 COLUMNS ( baz int ) + ) + ) + PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)) +) jt; + +-- JSON_TABLE: plan execution + +CREATE TEMP TABLE json_table_test (js text); + +INSERT INTO json_table_test +VALUES ( + '[ + {"a": 1, "b": [], "c": []}, + {"a": 2, "b": [1, 2, 3], "c": [10, null, 20]}, + {"a": 3, "b": [1, 2], "c": []}, + {"x": "4", "b": [1, 2], "c": 123} + ]' +); + +-- unspecified plan (outer, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + ) jt; + +-- default plan (outer, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, union) + ) jt; + +-- specific plan (p outer (pb union pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb union pc)) + ) jt; + +-- specific plan (p outer (pc union pb)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pc union pb)) + ) jt; + +-- default plan (inner, union) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (inner) + ) jt; + +-- specific plan (p inner (pb union pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb union pc)) + ) jt; + +-- default plan (inner, cross) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (cross, inner) + ) jt; + +-- specific plan (p inner (pb cross pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p inner (pb cross pc)) + ) jt; + +-- default plan (outer, cross) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan default (outer, cross) + ) jt; + +-- specific plan (p outer (pb cross pc)) +select + jt.* +from + json_table_test jtt, + json_table ( + jtt.js FORMAT JSON,'strict $[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on empty, + nested path 'strict $.b[*]' as pb columns ( b int path '$' ), + nested path 'strict $.c[*]' as pc columns ( c int path '$' ) + ) + plan (p outer (pb cross pc)) + ) jt; + + +select + jt.*, b1 + 100 as b +from + json_table (json + '[ + {"a": 1, "b": [[1, 10], [2], [3, 30, 300]], "c": [1, null, 2]}, + {"a": 2, "b": [10, 20], "c": [1, null, 2]}, + {"x": "3", "b": [11, 22, 33, 44]} + ]', + '$[*]' as p + columns ( + n for ordinality, + a int path 'lax $.a' default -1 on error, + nested path 'strict $.b[*]' as pb columns ( + b text format json path '$', + nested path 'strict $[*]' as pb1 columns ( + b1 int path '$' + ) + ), + nested path 'strict $.c[*]' as pc columns ( + c text format json path '$', + nested path 'strict $[*]' as pc1 columns ( + c1 int path '$' + ) + ) + ) + --plan default(outer, cross) + plan(p outer ((pb inner pb1) cross (pc outer pc1))) + ) jt; + +-- Should succeed (JSON arguments are passed to root and nested paths) +SELECT * +FROM + generate_series(1, 4) x, + generate_series(1, 3) y, + JSON_TABLE(json + '[[1,2,3],[2,3,4,5],[3,4,5,6]]', + 'strict $[*] ? (@[*] < $x)' + PASSING x AS x, y AS y + COLUMNS ( + y text FORMAT JSON PATH '$', + NESTED PATH 'strict $[*] ? (@ >= $y)' + COLUMNS ( + z int PATH '$' + ) + ) + ) jt; + +-- Should fail (JSON arguments are not passed to column paths) +SELECT * +FROM JSON_TABLE( + json '[1,2,3]', + '$[*] ? (@ < $x)' + PASSING 10 AS x + COLUMNS (y text FORMAT JSON PATH '$ ? (@ < $x)') + ) jt; -- Extension: non-constant JSON path SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); @@ -314,3 +897,5 @@ SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a'); SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); +-- Should fail (not supported) +SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); From 27c4a3924b73c3b6e55f840814beb45503caf7de Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 25 Jan 2019 22:50:25 +0300 Subject: [PATCH 43/66] Add raw jbvArray and jbvObject support to jsonpath --- src/backend/utils/adt/jsonpath_exec.c | 116 +++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index b68ab562a5..3616313f48 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -409,6 +409,8 @@ static inline JsonbValue *JsonInitBinary(JsonbValue *jbv, Json *js); static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); +static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, + bool isJsonb); static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb, JsonItem *val); @@ -945,7 +947,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } case jpiKey: - if (JsonbType(jb) == jbvObject) + if (JsonItemIsObject(jb)) + { + JsonItem obj; + + jb = wrapJsonObjectOrArray(jb, &obj, cxt->isJsonb); + return executeItemOptUnwrapTarget(cxt, jsp, jb, found, unwrap); + } + else if (JsonItemIsBinary(jb) && + JsonContainerIsObject(JsonItemBinary(jb).data)) { JsonItem val; int keylen; @@ -1015,6 +1025,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, int innermostArraySize = cxt->innermostArraySize; int i; int size = JsonxArraySize(jb, cxt->isJsonb); + bool binary = JsonItemIsBinary(jb); bool singleton = size < 0; bool hasNext = jspGetNext(jsp, &elem); @@ -1073,7 +1084,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { jsi = jb; } - else + else if (binary) { jsi = getJsonArrayElement(jb, (uint32) index, cxt->isJsonb, &jsibuf); @@ -1081,6 +1092,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jsi == NULL) continue; } + else + { + jsi = JsonbValueToJsonItem(&JsonItemArray(jb).elems[index], + &jsibuf); + } if (!hasNext && !found) return jperOk; @@ -1143,11 +1159,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAnyKey: if (JsonbType(jb) == jbvObject) { + JsonItem bin; bool hasNext = jspGetNext(jsp, &elem); - if (!JsonItemIsBinary(jb)) - elog(ERROR, "invalid jsonb object type: %d", - JsonItemGetType(jb)); + jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb); return executeAnyItem (cxt, hasNext ? &elem : NULL, @@ -1212,8 +1227,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAny: { + JsonItem bin; bool hasNext = jspGetNext(jsp, &elem); + jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb); + /* first try without any intermediate steps */ if (jsp->content.anybounds.first == 0) { @@ -1557,10 +1575,41 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, JsonValueList *found, bool unwrapElements) { - if (!JsonItemIsBinary(jb)) + if (JsonItemIsArray(jb)) { - Assert(!JsonItemIsArray(jb)); - elog(ERROR, "invalid jsonb array value type: %d", JsonItemGetType(jb)); + JsonPathExecResult res = jperNotFound; + JsonbValue *elem = JsonItemArray(jb).elems; + JsonbValue *last = elem + JsonItemArray(jb).nElems; + + for (; elem < last; elem++) + { + if (jsp) + { + JsonItem buf; + + res = executeItemOptUnwrapTarget(cxt, jsp, + JsonbValueToJsonItem(elem, &buf), + found, unwrapElements); + + if (jperIsError(res)) + break; + if (res == jperOk && !found) + break; + } + else + { + if (found) + { + JsonItem *jsi = palloc(sizeof(*jsi)); + + JsonValueListAppend(found, JsonbValueToJsonItem(elem, jsi)); + } + else + return jperOk; + } + } + + return res; } return executeAnyItem @@ -1639,8 +1688,6 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueListInitIterator(&seq, &it); while ((item = JsonValueListNext(&seq, &it))) { - Assert(!JsonItemIsArray(item)); - if (JsonbType(item) == jbvArray) executeItemUnwrapTargetArray(cxt, NULL, item, found, false); else @@ -2266,6 +2313,7 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathExecResult res = jperNotFound; JsonPathItem next; JsonbContainer *jbc; + JsonItem bin; JsonbValue key; JsonbValue val; JsonbValue idval; @@ -2278,12 +2326,13 @@ executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, int64 id; bool hasNext; - if (JsonbType(jb) != jbvObject || !JsonItemIsBinary(jb)) + if (JsonbType(jb) != jbvObject) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND), errmsg("jsonpath item method .%s() can only be applied to an object", jspOperationName(jsp->type))))); + jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb); jbc = JsonItemBinary(jb).data; if (!JsonContainerSize(jbc)) @@ -2529,7 +2578,8 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, static int JsonxArraySize(JsonItem *jb, bool isJsonb) { - Assert(!JsonItemIsArray(jb)); + if (JsonItemIsArray(jb)) + return JsonItemArray(jb).nElems; if (JsonItemIsBinary(jb)) { @@ -2897,6 +2947,41 @@ JsonInitBinary(JsonbValue *jbv, Json *js) return jbv; } +/* + * Transform a JsonbValue into a binary JsonbValue by encoding it to a + * binary jsonb container. + */ +static JsonItem * +JsonxWrapInBinary(JsonItem *jsi, JsonItem *out, bool isJsonb) +{ + if (!out) + out = palloc(sizeof(*out)); + + if (isJsonb) + { + Jsonb *jb = JsonItemToJsonb(jsi); + + JsonbInitBinary(JsonItemJbv(out), jb); + } + else + { + Json *js = JsonItemToJson(jsi); + + JsonInitBinary(JsonItemJbv(out), js); + } + + return out; +} + +static JsonItem * +wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb) +{ + if (!JsonItemIsObject(js) && !JsonItemIsArray(js)) + return js; + + return JsonxWrapInBinary(js, buf, isJsonb); +} + /* * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is. */ @@ -3096,7 +3181,6 @@ wrapItemsInArray(const JsonValueList *items, bool isJsonb) JsonbParseState *ps = NULL; JsonValueListIterator it; JsonItem *jsi; - JsonbValue jbv; JsonBuilderFunc push = isJsonb ? pushJsonbValue : pushJsonValue; push(&ps, WJB_BEGIN_ARRAY, NULL); @@ -3104,7 +3188,13 @@ wrapItemsInArray(const JsonValueList *items, bool isJsonb) JsonValueListInitIterator(items, &it); while ((jsi = JsonValueListNext(items, &it))) + { + JsonItem bin; + JsonbValue jbv; + + jsi = wrapJsonObjectOrArray(jsi, &bin, isJsonb); push(&ps, WJB_ELEM, JsonItemToJsonbValue(jsi, &jbv)); + } return push(&ps, WJB_END_ARRAY, NULL); } From eafa131bfe1270a21905708b3e1f1c6330dadb32 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 23 Mar 2017 01:33:10 +0300 Subject: [PATCH 44/66] Add jsonpath datetime() extension: UNIX epoch time to timestamptz --- src/backend/utils/adt/jsonpath_exec.c | 35 +++++++++++++- src/backend/utils/adt/timestamp.c | 51 ++++++++++++++++---- src/include/utils/timestamp.h | 2 + src/test/regress/expected/jsonb_jsonpath.out | 29 ++++++++--- src/test/regress/sql/jsonb_jsonpath.sql | 6 ++- 5 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 3616313f48..9ec6689333 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1413,10 +1413,41 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); - if (!(jb = getScalar(jb, jbvString))) + if (JsonItemIsNumeric(jb)) + { + bool error = false; + float8 unix_epoch = + DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow, + JsonItemNumericDatum(jb))); + TimestampTz tstz = float8_timestamptz_internal(unix_epoch, + &error); + + if (error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), + errmsg("UNIX epoch is out ouf timestamptz range")))); + + value = TimestampTzGetDatum(tstz); + typid = TIMESTAMPTZOID; + tz = 0; + res = jperOk; + + hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found) + break; + + jb = hasNext ? &jbvbuf : palloc(sizeof(*jb)); + + JsonItemInitDatetime(jb, value, typid, typmod, tz); + + res = executeNextItem(cxt, jsp, &elem, jb, found, hasNext); + break; + } + else if (!JsonItemIsString(jb)) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION), - errmsg("jsonpath item method .%s() can only be applied to a string value", + errmsg("jsonpath item method .%s() can only be applied to a string or number", jspOperationName(jsp->type))))); datetime = cstring_to_text_with_len(JsonItemString(jb).val, diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index cd104246cd..c2ffd680b9 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -728,21 +728,25 @@ make_timestamptz_at_timezone(PG_FUNCTION_ARGS) PG_RETURN_TIMESTAMPTZ(result); } -/* - * to_timestamp(double precision) - * Convert UNIX epoch to timestamptz. - */ -Datum -float8_timestamptz(PG_FUNCTION_ARGS) +TimestampTz +float8_timestamptz_internal(float8 seconds, bool *error) { - float8 seconds = PG_GETARG_FLOAT8(0); + float8 saved_seconds = seconds; TimestampTz result; /* Deal with NaN and infinite inputs ... */ if (isnan(seconds)) + { + if (error) + { + *error = true; + return 0; + } + ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp cannot be NaN"))); + } if (isinf(seconds)) { @@ -758,9 +762,17 @@ float8_timestamptz(PG_FUNCTION_ARGS) (float8) SECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE) || seconds >= (float8) SECS_PER_DAY * (TIMESTAMP_END_JULIAN - UNIX_EPOCH_JDATE)) + { + if (error) + { + *error = true; + return 0; + } + ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range: \"%g\"", seconds))); + } /* Convert UNIX epoch to Postgres epoch */ seconds -= ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY); @@ -770,13 +782,32 @@ float8_timestamptz(PG_FUNCTION_ARGS) /* Recheck in case roundoff produces something just out of range */ if (!IS_VALID_TIMESTAMP(result)) + { + if (error) + { + *error = true; + return 0; + } + ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range: \"%g\"", - PG_GETARG_FLOAT8(0)))); + errmsg("timestamp out of range: \"%g\"", saved_seconds))); + } } - PG_RETURN_TIMESTAMP(result); + return result; +} + +/* + * to_timestamp(double precision) + * Convert UNIX epoch to timestamptz. + */ +Datum +float8_timestamptz(PG_FUNCTION_ARGS) +{ + float8 seconds = PG_GETARG_FLOAT8(0); + + PG_RETURN_TIMESTAMP(float8_timestamptz_internal(seconds, NULL)); } /* timestamptz_out() diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index 077835fb1c..2b2a7a8c6f 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -100,6 +100,8 @@ extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); extern TimestampTz timestamp2timestamptz_internal(Timestamp timestamp, int *tzp, bool *error); +extern TimestampTz float8_timestamptz_internal(float8 seconds, bool *error); + extern int isoweek2j(int year, int week); extern void isoweek2date(int woy, int *year, int *mon, int *mday); extern void isoweekdate2date(int isoweek, int wday, int *year, int *mon, int *mday); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 9570a6ac81..56326bbe2e 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1659,20 +1659,18 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik (1 row) select jsonb_path_query('null', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select jsonb_path_query('true', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value -select jsonb_path_query('1', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select jsonb_path_query('[]', '$.datetime()'); jsonb_path_query ------------------ (0 rows) select jsonb_path_query('[]', 'strict $.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select jsonb_path_query('{}', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select jsonb_path_query('""', '$.datetime()'); ERROR: unrecognized datetime format HINT: use datetime template argument for explicit format specification @@ -1709,6 +1707,25 @@ ERROR: timezone argument of jsonpath item method .datetime() is out of integer select jsonb_path_query('"aaaa"', '$.datetime("HH24")'); ERROR: invalid value "aa" for "HH24" DETAIL: Value must be an integer. +-- Standard extension: UNIX epoch to timestamptz +select jsonb_path_query('0', '$.datetime()'); + jsonb_path_query +----------------------------- + "1970-01-01T00:00:00+00:00" +(1 row) + +select jsonb_path_query('0', '$.datetime().type()'); + jsonb_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select jsonb_path_query('1490216035.5', '$.datetime()'); + jsonb_path_query +------------------------------- + "2017-03-22T20:53:55.5+00:00" +(1 row) + select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; ?column? ---------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 8d08525a75..114dd096fd 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -348,7 +348,6 @@ select jsonb_path_query('[null, 1, "a\b", "a\\b", "^a\\b$"]', 'lax $[*] ? (@ lik select jsonb_path_query('null', '$.datetime()'); select jsonb_path_query('true', '$.datetime()'); -select jsonb_path_query('1', '$.datetime()'); select jsonb_path_query('[]', '$.datetime()'); select jsonb_path_query('[]', 'strict $.datetime()'); select jsonb_path_query('{}', '$.datetime()'); @@ -364,6 +363,11 @@ select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); select jsonb_path_query('"aaaa"', '$.datetime("HH24")'); +-- Standard extension: UNIX epoch to timestamptz +select jsonb_path_query('0', '$.datetime()'); +select jsonb_path_query('0', '$.datetime().type()'); +select jsonb_path_query('1490216035.5', '$.datetime()'); + select jsonb '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); From 9dbe6d1c1f0ae53007ec543e98230389b40e2c3d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 5 Apr 2017 22:53:12 +0300 Subject: [PATCH 45/66] Add jsonpath sequences --- src/backend/utils/adt/jsonpath.c | 66 ++++++++++++++++++-- src/backend/utils/adt/jsonpath_exec.c | 46 ++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 32 +++++++++- src/include/utils/jsonpath.h | 12 ++++ src/test/regress/expected/jsonb_jsonpath.out | 56 +++++++++++++++++ src/test/regress/expected/jsonpath.out | 36 +++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 9 +++ src/test/regress/sql/jsonpath.sql | 7 +++ 8 files changed, 257 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 8fba0e97d0..ee3fb52db0 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -216,7 +216,7 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) appendBinaryStringInfo(out, "strict ", 7); jspInit(&v, in); - printJsonPathItem(out, &v, false, true); + printJsonPathItem(out, &v, false, v.type != jpiSequence); return out->data; } @@ -417,6 +417,29 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiDouble: case jpiKeyValue: break; + case jpiSequence: + { + int32 nelems = list_length(item->value.sequence.elems); + ListCell *lc; + int offset; + + appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); + + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * nelems); + + foreach(lc, item->value.sequence.elems) + { + int32 elempos = + flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel, + insideArraySubscript); + + *(int32 *) &buf->data[offset] = elempos - pos; + offset += sizeof(int32); + } + } + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", item->type); } @@ -639,12 +662,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, if (i) appendStringInfoChar(buf, ','); - printJsonPathItem(buf, &from, false, false); + printJsonPathItem(buf, &from, false, from.type == jpiSequence); if (range) { appendBinaryStringInfo(buf, " to ", 4); - printJsonPathItem(buf, &to, false, false); + printJsonPathItem(buf, &to, false, to.type == jpiSequence); } } appendStringInfoChar(buf, ']'); @@ -712,6 +735,25 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, case jpiKeyValue: appendBinaryStringInfo(buf, ".keyvalue()", 11); break; + case jpiSequence: + if (printBracketes || jspHasNext(v)) + appendStringInfoChar(buf, '('); + + for (i = 0; i < v->content.sequence.nelems; i++) + { + JsonPathItem elem; + + if (i) + appendBinaryStringInfo(buf, ", ", 2); + + jspGetSequenceElement(v, i, &elem); + + printJsonPathItem(buf, &elem, false, elem.type == jpiSequence); + } + + if (printBracketes || jspHasNext(v)) + appendStringInfoChar(buf, ')'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -784,6 +826,8 @@ operationPriority(JsonPathItemType op) { switch (op) { + case jpiSequence: + return -1; case jpiOr: return 0; case jpiAnd: @@ -920,6 +964,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32(v->content.anybounds.first, base, pos); read_int32(v->content.anybounds.last, base, pos); break; + case jpiSequence: + read_int32(v->content.sequence.nelems, base, pos); + read_int32_n(v->content.sequence.elems, base, pos, + v->content.sequence.nelems); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -983,7 +1032,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiDouble || v->type == jpiDatetime || v->type == jpiKeyValue || - v->type == jpiStartsWith); + v->type == jpiStartsWith || + v->type == jpiSequence); if (a) jspInitByBuffer(a, v->base, v->nextPos); @@ -1080,3 +1130,11 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, return true; } + +void +jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem) +{ + Assert(v->type == jpiSequence); + + jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]); +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 9ec6689333..b00a9044f5 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1591,6 +1591,52 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeKeyValueMethod(cxt, jsp, jb, found); + case jpiSequence: + { + JsonPathItem next; + bool hasNext = jspGetNext(jsp, &next); + JsonValueList list; + JsonValueList *plist = hasNext ? &list : found; + JsonValueListIterator it; + int i; + + for (i = 0; i < jsp->content.sequence.nelems; i++) + { + JsonItem *v; + + if (hasNext) + memset(&list, 0, sizeof(list)); + + jspGetSequenceElement(jsp, i, &elem); + res = executeItem(cxt, &elem, jb, plist); + + if (jperIsError(res)) + break; + + if (!hasNext) + { + if (!found && res == jperOk) + break; + continue; + } + + JsonValueListInitIterator(&list, &it); + + while ((v = JsonValueListNext(&list, &it))) + { + res = executeItem(cxt, &next, v, found); + + if (jperIsError(res) || (!found && res == jperOk)) + { + i = jsp->content.sequence.nelems; + break; + } + } + } + + break; + } + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index ae06e18560..e7093e7744 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -56,6 +56,7 @@ static JsonPathParseItem *makeAny(int first, int last); static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, JsonPathString *flags); +static JsonPathParseItem *makeItemSequence(List *elems); /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -101,9 +102,9 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, %type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial expr_or_predicate - datetime_template opt_datetime_template + datetime_template opt_datetime_template expr_seq expr_or_seq -%type accessor_expr +%type accessor_expr expr_list %type index_list @@ -127,7 +128,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, %% result: - mode expr_or_predicate { + mode expr_or_seq { *result = palloc(sizeof(JsonPathParseResult)); (*result)->expr = $2; (*result)->lax = $1; @@ -140,6 +141,20 @@ expr_or_predicate: | predicate { $$ = $1; } ; +expr_or_seq: + expr_or_predicate { $$ = $1; } + | expr_seq { $$ = $1; } + ; + +expr_seq: + expr_list { $$ = makeItemSequence($1); } + ; + +expr_list: + expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); } + | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); } + ; + mode: STRICT_P { $$ = false; } | LAX_P { $$ = true; } @@ -195,6 +210,7 @@ path_primary: | '$' { $$ = makeItemType(jpiRoot); } | '@' { $$ = makeItemType(jpiCurrent); } | LAST_P { $$ = makeItemType(jpiLast); } + | '(' expr_seq ')' { $$ = $2; } ; accessor_expr: @@ -552,6 +568,16 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, return v; } +static JsonPathParseItem * +makeItemSequence(List *elems) +{ + JsonPathParseItem *v = makeItemType(jpiSequence); + + v->value.sequence.elems = elems; + + return v; +} + /* * jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is * unavoidable because jsonpath_gram does not create a .h file to export its diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 28717a6a41..6ef2bbba50 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -89,6 +89,7 @@ typedef enum JsonPathItemType jpiLast, /* LAST array subscript */ jpiStartsWith, /* STARTS WITH predicate */ jpiLikeRegex, /* LIKE_REGEX predicate */ + jpiSequence, /* sequence constructor: 'expr, ...' */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ @@ -149,6 +150,12 @@ typedef struct JsonPathItem uint32 last; } anybounds; + struct + { + int32 nelems; + int32 *elems; + } sequence; + struct { char *data; /* for bool, numeric and string/key */ @@ -178,6 +185,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 void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem); extern const char *jspOperationName(JsonPathItemType type); @@ -231,6 +239,10 @@ struct JsonPathParseItem uint32 flags; } like_regex; + struct { + List *elems; + } sequence; + /* scalars */ Numeric numeric; bool boolean; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 56326bbe2e..694a2a71a6 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2375,3 +2375,59 @@ SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1'); t (1 row) +-- extension: path sequences +select jsonb_path_query('[1,2,3,4,5]', '10, 20, $[*], 30'); + jsonb_path_query +------------------ + 10 + 20 + 1 + 2 + 3 + 4 + 5 + 30 +(8 rows) + +select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30'); + jsonb_path_query +------------------ + 10 + 20 + 30 +(3 rows) + +select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30'); +ERROR: SQL/JSON member not found +DETAIL: jsonpath member accessor can only be applied to an object +select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); + jsonb_path_query +------------------ + -10 + -20 + -2 + -3 + -4 + -30 +(6 rows) + +select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()'); + jsonb_path_query +------------------ + 10 + 20.5 + 2 + 3 + 4 + 30 +(6 rows) + +select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); + jsonb_path_query +------------------ + 4 +(1 row) + +select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); +ERROR: invalid SQL/JSON subscript +DETAIL: jsonpath array subscript is not a singleton numeric value diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index fb9fbf2154..6381da0be3 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -568,6 +568,42 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath; (($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c"))) (1 row) +select '1, 2 + 3, $.a[*] + 5'::jsonpath; + jsonpath +------------------------ + 1, 2 + 3, $."a"[*] + 5 +(1 row) + +select '(1, 2, $.a)'::jsonpath; + jsonpath +------------- + 1, 2, $."a" +(1 row) + +select '(1, 2, $.a).a[*]'::jsonpath; + jsonpath +---------------------- + (1, 2, $."a")."a"[*] +(1 row) + +select '(1, 2, $.a) == 5'::jsonpath; + jsonpath +---------------------- + ((1, 2, $."a") == 5) +(1 row) + +select '$[(1, 2, $.a) to (3, 4)]'::jsonpath; + jsonpath +---------------------------- + $[(1, 2, $."a") to (3, 4)] +(1 row) + +select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; + jsonpath +----------------------------- + $[(1, (2, $."a")),3,(4, 5)] +(1 row) + select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 114dd096fd..2467a2f2b5 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -539,3 +539,12 @@ SELECT jsonb_path_match('[true, true]', '$[*]', silent => false); SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1'); + +-- extension: path sequences +select jsonb_path_query('[1,2,3,4,5]', '10, 20, $[*], 30'); +select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30'); +select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30'); +select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); +select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()'); +select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); +select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index e65ec0236c..3464b35aa2 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -106,6 +106,13 @@ select '($)'::jsonpath; select '(($))'::jsonpath; select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath; +select '1, 2 + 3, $.a[*] + 5'::jsonpath; +select '(1, 2, $.a)'::jsonpath; +select '(1, 2, $.a).a[*]'::jsonpath; +select '(1, 2, $.a) == 5'::jsonpath; +select '$[(1, 2, $.a) to (3, 4)]'::jsonpath; +select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; + select '$ ? (@.a < 1)'::jsonpath; select '$ ? (@.a < -1)'::jsonpath; select '$ ? (@.a < +1)'::jsonpath; From 9d1d6114097082c810a8a0c92a4cae972bfbfb2e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Apr 2017 22:36:05 +0300 Subject: [PATCH 46/66] Add jsonpath array constructors --- src/backend/utils/adt/jsonpath.c | 20 ++++++- src/backend/utils/adt/jsonpath_exec.c | 23 ++++++++ src/backend/utils/adt/jsonpath_gram.y | 2 + src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 58 ++++++++++++++++++++ src/test/regress/expected/jsonpath.out | 12 ++++ src/test/regress/sql/jsonb_jsonpath.sql | 11 ++++ src/test/regress/sql/jsonpath.sql | 3 + 8 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index ee3fb52db0..2f7bca283f 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -338,9 +338,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiPlus: case jpiMinus: case jpiExists: + case jpiArray: { int32 arg = reserveSpaceForItemPointer(buf); + if (!item->value.arg) + break; + chld = flattenJsonPathParseItem(buf, item->value.arg, nestingLevel + argNestingLevel, insideArraySubscript); @@ -754,6 +758,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, if (printBracketes || jspHasNext(v)) appendStringInfoChar(buf, ')'); break; + case jpiArray: + appendStringInfoChar(buf, '['); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ']'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -953,6 +966,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiPlus: case jpiMinus: case jpiFilter: + case jpiArray: read_int32(v->content.arg, base, pos); break; case jpiIndexArray: @@ -982,7 +996,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiIsUnknown || v->type == jpiExists || v->type == jpiPlus || - v->type == jpiMinus); + v->type == jpiMinus || + v->type == jpiArray); jspInitByBuffer(a, v->base, v->content.arg); } @@ -1033,7 +1048,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiDatetime || v->type == jpiKeyValue || v->type == jpiStartsWith || - v->type == jpiSequence); + v->type == jpiSequence || + v->type == jpiArray); if (a) jspInitByBuffer(a, v->base, v->nextPos); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index b00a9044f5..04ee5a7554 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1637,6 +1637,29 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } + case jpiArray: + { + JsonValueList list = {0}; + JsonbValue *arr; + JsonItem jsi; + + if (jsp->content.arg) + { + jspGetArg(jsp, &elem); + res = executeItem(cxt, &elem, jb, &list); + + if (jperIsError(res)) + break; + } + + arr = wrapItemsInArray(&list, cxt->isJsonb); + + res = executeNextItem(cxt, jsp, NULL, + JsonbValueToJsonItem(arr, &jsi), + found, true); + break; + } + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index e7093e7744..69043a4725 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -211,6 +211,8 @@ path_primary: | '@' { $$ = makeItemType(jpiCurrent); } | LAST_P { $$ = makeItemType(jpiLast); } | '(' expr_seq ')' { $$ = $2; } + | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); } + | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); } ; accessor_expr: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 6ef2bbba50..a35da88911 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -90,6 +90,7 @@ typedef enum JsonPathItemType jpiStartsWith, /* STARTS WITH predicate */ jpiLikeRegex, /* LIKE_REGEX predicate */ jpiSequence, /* sequence constructor: 'expr, ...' */ + jpiArray, /* array constructor: '[expr, ...]' */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 694a2a71a6..6550174117 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2213,6 +2213,12 @@ SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); ------------------ (0 rows) +SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]'); + jsonb_path_query +------------------ + [1, 2] +(1 row) + SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -2245,6 +2251,12 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*]. [] (1 row) +SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]'); + jsonb_path_query_array +------------------------ + [[1, 2]] +(1 row) + SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true); @@ -2431,3 +2443,49 @@ select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); ERROR: invalid SQL/JSON subscript DETAIL: jsonpath array subscript is not a singleton numeric value +-- extension: array constructors +select jsonb_path_query('[1, 2, 3]', '[]'); + jsonb_path_query +------------------ + [] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]'); + jsonb_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]'); + jsonb_path_query +------------------ + 1 + 2 + 1 + 2 + 3 + 4 + 5 +(7 rows) + +select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]'); + jsonb_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); + jsonb_path_query +------------------------------- + [[1, 2], [1, 2, 3, 4], 5, []] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); +ERROR: SQL/JSON member not found +DETAIL: jsonpath member accessor can only be applied to an object +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); + jsonb_path_query +------------------ + [4, 5, 6, 7] +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 6381da0be3..0a5bd4fd6a 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -604,6 +604,18 @@ select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; $[(1, (2, $."a")),3,(4, 5)] (1 row) +select '[]'::jsonpath; + jsonpath +---------- + [] +(1 row) + +select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; + jsonpath +------------------------------------------ + [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]] +(1 row) + select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 2467a2f2b5..762e449085 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -502,6 +502,7 @@ set time zone default; SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); +SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -509,6 +510,7 @@ SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 1, "max": 4}'); SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', vars => '{"min": 3, "max": 4}'); +SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]'); SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a', silent => true); @@ -548,3 +550,12 @@ select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()'); select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); + +-- extension: array constructors +select jsonb_path_query('[1, 2, 3]', '[]'); +select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]'); +select jsonb_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]'); +select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]'); +select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); +select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 3464b35aa2..3bf8dbae0c 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -113,6 +113,9 @@ select '(1, 2, $.a) == 5'::jsonpath; select '$[(1, 2, $.a) to (3, 4)]'::jsonpath; select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; +select '[]'::jsonpath; +select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; + select '$ ? (@.a < 1)'::jsonpath; select '$ ? (@.a < -1)'::jsonpath; select '$ ? (@.a < +1)'::jsonpath; From f8f1a7deff30197491c47234e8f0edc25ae05780 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Apr 2017 23:34:14 +0300 Subject: [PATCH 47/66] Add jsonpath object constructors --- src/backend/utils/adt/jsonpath.c | 68 +++++++++++++++++++- src/backend/utils/adt/jsonpath_exec.c | 59 +++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 26 +++++++- src/include/utils/jsonpath.h | 18 ++++++ src/test/regress/expected/jsonb_jsonpath.out | 34 ++++++++++ src/test/regress/expected/jsonpath.out | 18 ++++++ src/test/regress/sql/jsonb_jsonpath.sql | 8 +++ src/test/regress/sql/jsonpath.sql | 4 ++ 8 files changed, 233 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 2f7bca283f..746087c564 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -444,6 +444,38 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, } } break; + case jpiObject: + { + int32 nfields = list_length(item->value.object.fields); + ListCell *lc; + int offset; + + appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields)); + + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields); + + foreach(lc, item->value.object.fields) + { + JsonPathParseItem *field = lfirst(lc); + int32 keypos = + flattenJsonPathParseItem(buf, field->value.args.left, + nestingLevel, + insideArraySubscript); + int32 valpos = + flattenJsonPathParseItem(buf, field->value.args.right, + nestingLevel, + insideArraySubscript); + int32 *ppos = (int32 *) &buf->data[offset]; + + ppos[0] = keypos - pos; + ppos[1] = valpos - pos; + + offset += 2 * sizeof(int32); + } + } + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", item->type); } @@ -767,6 +799,26 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, } appendStringInfoChar(buf, ']'); break; + case jpiObject: + appendStringInfoChar(buf, '{'); + + for (i = 0; i < v->content.object.nfields; i++) + { + JsonPathItem key; + JsonPathItem val; + + jspGetObjectField(v, i, &key, &val); + + if (i) + appendBinaryStringInfo(buf, ", ", 2); + + printJsonPathItem(buf, &key, false, false); + appendBinaryStringInfo(buf, ": ", 2); + printJsonPathItem(buf, &val, false, val.type == jpiSequence); + } + + appendStringInfoChar(buf, '}'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -983,6 +1035,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32_n(v->content.sequence.elems, base, pos, v->content.sequence.nelems); break; + case jpiObject: + read_int32(v->content.object.nfields, base, pos); + read_int32_n(v->content.object.fields, base, pos, + v->content.object.nfields * 2); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -1049,7 +1106,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiKeyValue || v->type == jpiStartsWith || v->type == jpiSequence || - v->type == jpiArray); + v->type == jpiArray || + v->type == jpiObject); if (a) jspInitByBuffer(a, v->base, v->nextPos); @@ -1154,3 +1212,11 @@ jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem) jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]); } + +void +jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val) +{ + Assert(v->type == jpiObject); + jspInitByBuffer(key, v->base, v->content.object.fields[i].key); + jspInitByBuffer(val, v->base, v->content.object.fields[i].val); +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 04ee5a7554..e58fd14b52 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1660,6 +1660,65 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } + case jpiObject: + { + JsonbParseState *ps = NULL; + JsonbValue *obj; + JsonItem jsibuf; + int i; + JsonBuilderFunc push = + cxt->isJsonb ? pushJsonbValue : pushJsonValue; + + push(&ps, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < jsp->content.object.nfields; i++) + { + JsonItem *jsi; + JsonItem jsitmp; + JsonPathItem key; + JsonPathItem val; + JsonbValue valjbv; + JsonValueList key_list = {0}; + JsonValueList val_list = {0}; + + jspGetObjectField(jsp, i, &key, &val); + + res = executeItem(cxt, &key, jb, &key_list); + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&key_list) != 1 || + !(jsi = getScalar(JsonValueListHead(&key_list), jbvString))) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg(ERRMSG_JSON_SCALAR_REQUIRED), + errdetail("key in jsonpath object constructor must be a singleton string")))); /* XXX */ + + pushJsonbValue(&ps, WJB_KEY, JsonItemJbv(jsi)); + + res = executeItem(cxt, &val, jb, &val_list); + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&val_list) != 1) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("value in jsonpath object constructor must be a singleton")))); + + jsi = JsonValueListHead(&val_list); + jsi = wrapJsonObjectOrArray(jsi, &jsitmp, cxt->isJsonb); + + push(&ps, WJB_VALUE, JsonItemToJsonbValue(jsi, &valjbv)); + } + + obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); + jb = JsonbValueToJsonItem(obj, &jsibuf); + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + break; + } + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 69043a4725..98c2b67518 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -57,6 +57,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern, JsonPathString *flags); static JsonPathParseItem *makeItemSequence(List *elems); +static JsonPathParseItem *makeItemObject(List *fields); /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -103,8 +104,9 @@ static JsonPathParseItem *makeItemSequence(List *elems); any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial expr_or_predicate datetime_template opt_datetime_template expr_seq expr_or_seq + object_field -%type accessor_expr expr_list +%type accessor_expr expr_list object_field_list %type index_list @@ -213,6 +215,18 @@ path_primary: | '(' expr_seq ')' { $$ = $2; } | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); } | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); } + | '{' object_field_list '}' { $$ = makeItemObject($2); } + ; + +object_field_list: + /* EMPTY */ { $$ = NIL; } + | object_field { $$ = list_make1($1); } + | object_field_list ',' object_field { $$ = lappend($1, $3); } + ; + +object_field: + key_name ':' expr_or_predicate + { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); } ; accessor_expr: @@ -580,6 +594,16 @@ makeItemSequence(List *elems) return v; } +static JsonPathParseItem * +makeItemObject(List *fields) +{ + JsonPathParseItem *v = makeItemType(jpiObject); + + v->value.object.fields = fields; + + return v; +} + /* * jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is * unavoidable because jsonpath_gram does not create a .h file to export its diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index a35da88911..cc39f8766b 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -91,6 +91,8 @@ typedef enum JsonPathItemType jpiLikeRegex, /* LIKE_REGEX predicate */ jpiSequence, /* sequence constructor: 'expr, ...' */ jpiArray, /* array constructor: '[expr, ...]' */ + jpiObject, /* object constructor: '{ key : value, ... }' */ + jpiObjectField, /* element of object constructor: 'key : value' */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ @@ -157,6 +159,16 @@ typedef struct JsonPathItem int32 *elems; } sequence; + struct + { + int32 nfields; + struct + { + int32 key; + int32 val; + } *fields; + } object; + struct { char *data; /* for bool, numeric and string/key */ @@ -187,6 +199,8 @@ extern char *jspGetString(JsonPathItem *v, int32 *len); extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, int i); extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem); +extern void jspGetObjectField(JsonPathItem *v, int i, + JsonPathItem *key, JsonPathItem *val); extern const char *jspOperationName(JsonPathItemType type); @@ -244,6 +258,10 @@ struct JsonPathParseItem List *elems; } sequence; + struct { + List *fields; + } object; + /* scalars */ Numeric numeric; bool boolean; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 6550174117..b15b9e2580 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2489,3 +2489,37 @@ select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]' [4, 5, 6, 7] (1 row) +-- extension: object constructors +select jsonb_path_query('[1, 2, 3]', '{}'); + jsonb_path_query +------------------ + {} +(1 row) + +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}'); + jsonb_path_query +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*'); + jsonb_path_query +------------------ + 5 + [1, 2, 3, 4, 5] +(2 rows) + +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'); + jsonb_path_query +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}'); +ERROR: value in jsonpath object constructor must be a singleton +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'); + jsonb_path_query +--------------------------------------------------------- + {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}} +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 0a5bd4fd6a..71daf96bce 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -616,6 +616,24 @@ select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]] (1 row) +select '{}'::jsonpath; + jsonpath +---------- + {} +(1 row) + +select '{a: 1 + 2}'::jsonpath; + jsonpath +-------------- + {"a": 1 + 2} +(1 row) + +select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath; + jsonpath +----------------------------------------------------------------------- + {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}} +(1 row) + select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 762e449085..e87b2d3fef 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -559,3 +559,11 @@ select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]'); select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); + +-- extension: object constructors +select jsonb_path_query('[1, 2, 3]', '{}'); +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}'); +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*'); +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'); +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}'); +select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 3bf8dbae0c..0938123348 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -116,6 +116,10 @@ select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath; select '[]'::jsonpath; select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath; +select '{}'::jsonpath; +select '{a: 1 + 2}'::jsonpath; +select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath; + select '$ ? (@.a < 1)'::jsonpath; select '$ ? (@.a < -1)'::jsonpath; select '$ ? (@.a < +1)'::jsonpath; From 84830c977dad8915589a2b6bec938296e5646d92 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 12 May 2017 00:07:24 +0300 Subject: [PATCH 48/66] Add jsonpath object subscripting --- src/backend/utils/adt/jsonpath_exec.c | 135 ++++++++++++++++++- src/test/regress/expected/jsonb_jsonpath.out | 107 ++++++++++++++- src/test/regress/sql/jsonb_jsonpath.sql | 25 ++++ 3 files changed, 254 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index e58fd14b52..0de29663e7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1020,7 +1020,132 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiIndexArray: - if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt)) + if (JsonbType(jb) == jbvObject) + { + int innermostArraySize = cxt->innermostArraySize; + int i; + JsonItem bin; + + jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb); + + cxt->innermostArraySize = 1; + + for (i = 0; i < jsp->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + JsonItem *key; + JsonValueList keys = { 0 }; + bool range = jspGetArraySubscript(jsp, &from, &to, i); + + if (range) + { + int index_from; + int index_to; + + if (!jspAutoWrap(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND), + errmsg("jsonpath array accessor can only be applied to an array")))); + + res = getArrayIndex(cxt, &from, jb, &index_from); + if (jperIsError(res)) + return res; + + res = getArrayIndex(cxt, &to, jb, &index_to); + if (jperIsError(res)) + return res; + + res = jperNotFound; + + if (index_from <= 0 && index_to >= 0) + { + res = executeNextItem(cxt, jsp, NULL, jb, found, + true); + if (jperIsError(res)) + return res; + } + + if (res == jperOk && !found) + break; + + continue; + } + + res = executeItem(cxt, &from, jb, &keys); + + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&keys) != 1) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT), + errmsg("object subscript must be a singleton value")))); + + key = JsonValueListHead(&keys); + /* key = extractScalar(key, &tmp); XXX */ + + res = jperNotFound; + + if (JsonItemIsNumeric(key) && jspAutoWrap(cxt)) + { + Datum index_datum = + DirectFunctionCall2(numeric_trunc, JsonItemNumericDatum(key), Int32GetDatum(0)); + bool have_error = false; + int index = + numeric_int4_opt_error(DatumGetNumeric(index_datum), + &have_error); + + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT), + errmsg("jsonpath array subscript is out integer range")))); + + if (!index) + { + res = executeNextItem(cxt, jsp, NULL, jb, found, + true); + if (jperIsError(res)) + return res; + } + else if (!jspIgnoreStructuralErrors(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT), + errmsg("jsonpath array subscript is out of bounds")))); + } + else if (JsonItemIsString(key)) + { + JsonItem valbuf; + JsonItem *val; + + val = getJsonObjectKey(jb, JsonItemString(key).val, + JsonItemString(key).len, + cxt->isJsonb, &valbuf); + + if (val) + { + res = executeNextItem(cxt, jsp, NULL, val, found, + true); + if (jperIsError(res)) + return res; + } + else if (!jspIgnoreStructuralErrors(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), + errmsg("JSON object does not contain the specified key")))); + } + else if (!jspIgnoreStructuralErrors(cxt)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT), + errmsg("object subscript must be a string or number")))); + + if (res == jperOk && !found) + break; + } + + cxt->innermostArraySize = innermostArraySize; + } + else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt)) { int innermostArraySize = cxt->innermostArraySize; int i; @@ -1124,7 +1249,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, { RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND), - errmsg("jsonpath array accessor can only be applied to an array")))); + errmsg("jsonpath array accessor can only be applied to an array or object")))); } break; @@ -1691,8 +1816,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, !(jsi = getScalar(JsonValueListHead(&key_list), jbvString))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_JSON_SCALAR_REQUIRED), - errmsg(ERRMSG_JSON_SCALAR_REQUIRED), - errdetail("key in jsonpath object constructor must be a singleton string")))); /* XXX */ + errmsg("key in jsonpath object constructor must be a singleton string")))); /* XXX */ pushJsonbValue(&ps, WJB_KEY, JsonItemJbv(jsi)); @@ -1703,8 +1827,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (JsonValueListLength(&val_list) != 1) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), - errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), - errdetail("value in jsonpath object constructor must be a singleton")))); + errmsg("value in jsonpath object constructor must be a singleton")))); jsi = JsonValueListHead(&val_list); jsi = wrapJsonObjectOrArray(jsi, &jsitmp, cxt->isJsonb); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index b15b9e2580..2ea0cb8687 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -177,6 +177,32 @@ select jsonb '[1]' @? 'strict $[1.2]'; (1 row) +select jsonb_path_query('[1]', 'strict $[1.2]'); +ERROR: jsonpath array subscript is out of bounds +select jsonb_path_query('{}', 'strict $[0.3]'); +ERROR: object subscript must be a string or number +select jsonb '{}' @? 'lax $[0.3]'; + ?column? +---------- + t +(1 row) + +select jsonb_path_query('{}', 'strict $[1.2]'); +ERROR: object subscript must be a string or number +select jsonb '{}' @? 'lax $[1.2]'; + ?column? +---------- + f +(1 row) + +select jsonb_path_query('{}', 'strict $[-2 to 3]'); +ERROR: jsonpath array accessor can only be applied to an array +select jsonb '{}' @? 'lax $[-2 to 3]'; + ?column? +---------- + t +(1 row) + select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; ?column? ---------- @@ -289,7 +315,7 @@ select jsonb_path_query('{}', 'strict $.a', silent => true); (0 rows) select jsonb_path_query('1', 'strict $[1]'); -ERROR: jsonpath array accessor can only be applied to an array +ERROR: jsonpath array accessor can only be applied to an array or object select jsonb_path_query('1', 'strict $[*]'); ERROR: jsonpath wildcard array accessor can only be applied to an array select jsonb_path_query('[]', 'strict $[1]'); @@ -404,6 +430,12 @@ select jsonb_path_query('1', 'lax $[*]'); 1 (1 row) +select jsonb_path_query('{}', 'lax $[0]'); + jsonb_path_query +------------------ + {} +(1 row) + select jsonb_path_query('[1]', 'lax $[0]'); jsonb_path_query ------------------ @@ -454,6 +486,12 @@ select jsonb_path_query('[1]', '$[last]'); 1 (1 row) +select jsonb_path_query('{}', '$[last]'); + jsonb_path_query +------------------ + {} +(1 row) + select jsonb_path_query('[1,2,3]', '$[last]'); jsonb_path_query ------------------ @@ -2410,8 +2448,7 @@ select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30'); (3 rows) select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30'); -ERROR: SQL/JSON member not found -DETAIL: jsonpath member accessor can only be applied to an object +ERROR: jsonpath member accessor can only be applied to an object select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); jsonb_path_query ------------------ @@ -2441,8 +2478,7 @@ select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); (1 row) select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); -ERROR: invalid SQL/JSON subscript -DETAIL: jsonpath array subscript is not a singleton numeric value +ERROR: jsonpath array subscript is not a single numeric value -- extension: array constructors select jsonb_path_query('[1, 2, 3]', '[]'); jsonb_path_query @@ -2481,8 +2517,7 @@ select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); (1 row) select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); -ERROR: SQL/JSON member not found -DETAIL: jsonpath member accessor can only be applied to an object +ERROR: jsonpath member accessor can only be applied to an object select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); jsonb_path_query ------------------ @@ -2523,3 +2558,61 @@ select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "fo {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}} (1 row) +-- extension: object subscripting +select jsonb '{"a": 1}' @? '$["a"]'; + ?column? +---------- + t +(1 row) + +select jsonb '{"a": 1}' @? '$["b"]'; + ?column? +---------- + f +(1 row) + +select jsonb '{"a": 1}' @? 'strict $["b"]'; + ?column? +---------- + +(1 row) + +select jsonb '{"a": 1}' @? '$["b", "a"]'; + ?column? +---------- + t +(1 row) + +select jsonb_path_query('{"a": 1}', '$["a"]'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('{"a": 1}', 'strict $["b"]'); +ERROR: JSON object does not contain the specified key +select jsonb_path_query('{"a": 1}', 'lax $["b"]'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]'); + jsonb_path_query +------------------ + 2 + 2 + 1 + {"a": 1, "b": 2} +(4 rows) + +select jsonb_path_query('null', '{"a": 1}["a"]'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('null', '{"a": 1}["b"]'); + jsonb_path_query +------------------ +(0 rows) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index e87b2d3fef..474b325751 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -30,6 +30,14 @@ select jsonb '[1]' @? '$[0.5]'; select jsonb '[1]' @? '$[0.9]'; select jsonb '[1]' @? '$[1.2]'; select jsonb '[1]' @? 'strict $[1.2]'; +select jsonb_path_query('[1]', 'strict $[1.2]'); +select jsonb_path_query('{}', 'strict $[0.3]'); +select jsonb '{}' @? 'lax $[0.3]'; +select jsonb_path_query('{}', 'strict $[1.2]'); +select jsonb '{}' @? 'lax $[1.2]'; +select jsonb_path_query('{}', 'strict $[-2 to 3]'); +select jsonb '{}' @? 'lax $[-2 to 3]'; + select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])'; select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])'; @@ -80,6 +88,7 @@ select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a'); select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]'); select jsonb_path_query('1', 'lax $[0]'); select jsonb_path_query('1', 'lax $[*]'); +select jsonb_path_query('{}', 'lax $[0]'); select jsonb_path_query('[1]', 'lax $[0]'); select jsonb_path_query('[1]', 'lax $[*]'); select jsonb_path_query('[1,2,3]', 'lax $[*]'); @@ -90,6 +99,7 @@ select jsonb_path_query('[]', '$[last ? (exists(last))]'); select jsonb_path_query('[]', 'strict $[last]'); select jsonb_path_query('[]', 'strict $[last]', silent => true); select jsonb_path_query('[1]', '$[last]'); +select jsonb_path_query('{}', '$[last]'); select jsonb_path_query('[1,2,3]', '$[last]'); select jsonb_path_query('[1,2,3]', '$[last - 1]'); select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]'); @@ -137,6 +147,7 @@ select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)'); + select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)'; select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)'; select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; @@ -567,3 +578,17 @@ select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*'); select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'); select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}'); select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'); + +-- extension: object subscripting +select jsonb '{"a": 1}' @? '$["a"]'; +select jsonb '{"a": 1}' @? '$["b"]'; +select jsonb '{"a": 1}' @? 'strict $["b"]'; +select jsonb '{"a": 1}' @? '$["b", "a"]'; + +select jsonb_path_query('{"a": 1}', '$["a"]'); +select jsonb_path_query('{"a": 1}', 'strict $["b"]'); +select jsonb_path_query('{"a": 1}', 'lax $["b"]'); +select jsonb_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]'); + +select jsonb_path_query('null', '{"a": 1}["a"]'); +select jsonb_path_query('null', '{"a": 1}["b"]'); From d1ae7d510638b11486209fb064f2205fb6f8cf32 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 23 Jan 2019 03:18:48 +0300 Subject: [PATCH 49/66] Add documentaion for jsonpath syntax extensions --- doc/src/sgml/func.sgml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index eaf367a5db..33ab515fc4 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11753,6 +11753,14 @@ table2-mapping + + + + Enclosing the path specification into square brackets + [] automatically wraps the path evaluation + result into an array. + + From ad16a760f375c5d9237b6ddc3f9e583e6ec3819f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:06:21 +0300 Subject: [PATCH 50/66] Add json tests for jsonpath extensions --- src/test/regress/expected/json_jsonpath.out | 272 +++++++++++++++++++- src/test/regress/sql/json_jsonpath.sql | 59 ++++- 2 files changed, 323 insertions(+), 8 deletions(-) diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index f3f6a0b8a7..918e109b0a 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -172,6 +172,32 @@ select json '[1]' @? 'strict $[1.2]'; (1 row) +select json_path_query('[1]', 'strict $[1.2]'); +ERROR: jsonpath array subscript is out of bounds +select json_path_query('{}', 'strict $[0.3]'); +ERROR: object subscript must be a string or number +select json '{}' @? 'lax $[0.3]'; + ?column? +---------- + t +(1 row) + +select json_path_query('{}', 'strict $[1.2]'); +ERROR: object subscript must be a string or number +select json '{}' @? 'lax $[1.2]'; + ?column? +---------- + f +(1 row) + +select json_path_query('{}', 'strict $[-2 to 3]'); +ERROR: jsonpath array accessor can only be applied to an array +select json '{}' @? 'lax $[-2 to 3]'; + ?column? +---------- + t +(1 row) + select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; ?column? ---------- @@ -244,7 +270,7 @@ select json_path_query('{}', 'lax $.a'); select json_path_query('{}', 'strict $.a'); ERROR: JSON object does not contain key "a" select json_path_query('1', 'strict $[1]'); -ERROR: jsonpath array accessor can only be applied to an array +ERROR: jsonpath array accessor can only be applied to an array or object select json_path_query('1', 'strict $[*]'); ERROR: jsonpath wildcard array accessor can only be applied to an array select json_path_query('[]', 'strict $[1]'); @@ -339,6 +365,12 @@ select json_path_query('1', 'lax $[*]'); 1 (1 row) +select json_path_query('{}', 'lax $[0]'); + json_path_query +----------------- + {} +(1 row) + select json_path_query('[1]', 'lax $[0]'); json_path_query ----------------- @@ -379,6 +411,12 @@ select json_path_query('[1]', '$[last]'); 1 (1 row) +select json_path_query('{}', '$[last]'); + json_path_query +----------------- + {} +(1 row) + select json_path_query('[1,2,3]', '$[last]'); json_path_query ----------------- @@ -1438,20 +1476,18 @@ select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "b (2 rows) select json_path_query('null', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select json_path_query('true', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value -select json_path_query('1', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select json_path_query('[]', '$.datetime()'); json_path_query ----------------- (0 rows) select json_path_query('[]', 'strict $.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select json_path_query('{}', '$.datetime()'); -ERROR: jsonpath item method .datetime() can only be applied to a string value +ERROR: jsonpath item method .datetime() can only be applied to a string or number select json_path_query('""', '$.datetime()'); ERROR: unrecognized datetime format HINT: use datetime template argument for explicit format specification @@ -1488,6 +1524,25 @@ ERROR: timezone argument of jsonpath item method .datetime() is out of integer select json_path_query('"aaaa"', '$.datetime("HH24")'); ERROR: invalid value "aa" for "HH24" DETAIL: Value must be an integer. +-- Standard extension: UNIX epoch to timestamptz +select json_path_query('0', '$.datetime()'); + json_path_query +----------------------------- + "1970-01-01T00:00:00+00:00" +(1 row) + +select json_path_query('0', '$.datetime().type()'); + json_path_query +---------------------------- + "timestamp with time zone" +(1 row) + +select json_path_query('1490216035.5', '$.datetime()'); + json_path_query +------------------------------- + "2017-03-22T20:53:55.5+00:00" +(1 row) + select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; ?column? ---------- @@ -1975,6 +2030,12 @@ SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); ----------------- (0 rows) +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]'); + json_path_query +----------------- + [1, 2] +(1 row) + SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -2007,6 +2068,12 @@ SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a [] (1 row) +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]'); + json_path_query_array +----------------------- + [[1, 2]] +(1 row) + SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); ERROR: JSON object does not contain key "a" SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -2075,3 +2142,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; f (1 row) +-- extension: path sequences +select json_path_query('[1,2,3,4,5]', '10, 20, $[*], 30'); + json_path_query +----------------- + 10 + 20 + 1 + 2 + 3 + 4 + 5 + 30 +(8 rows) + +select json_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30'); + json_path_query +----------------- + 10 + 20 + 30 +(3 rows) + +select json_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30'); +ERROR: jsonpath member accessor can only be applied to an object +select json_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); + json_path_query +----------------- + -10 + -20 + -2 + -3 + -4 + -30 +(6 rows) + +select json_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()'); + json_path_query +----------------- + 10 + 20.5 + 2 + 3 + 4 + 30 +(6 rows) + +select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); + json_path_query +----------------- + 4 +(1 row) + +select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); +ERROR: jsonpath array subscript is not a single numeric value +-- extension: array constructors +select json_path_query('[1, 2, 3]', '[]'); + json_path_query +----------------- + [] +(1 row) + +select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]'); + json_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]'); + json_path_query +----------------- + 1 + 2 + 1 + 2 + 3 + 4 + 5 +(7 rows) + +select json_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]'); + json_path_query +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select json_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); + json_path_query +------------------------------- + [[1, 2], [1, 2, 3, 4], 5, []] +(1 row) + +select json_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); +ERROR: jsonpath member accessor can only be applied to an object +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); + json_path_query +----------------- + [4, 5, 6, 7] +(1 row) + +-- extension: object constructors +select json_path_query('[1, 2, 3]', '{}'); + json_path_query +----------------- + {} +(1 row) + +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}'); + json_path_query +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*'); + json_path_query +----------------- + 5 + [1, 2, 3, 4, 5] +(2 rows) + +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'); + json_path_query +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}'); +ERROR: value in jsonpath object constructor must be a singleton +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'); + json_path_query +--------------------------------------------------------- + {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}} +(1 row) + +-- extension: object subscripting +select json '{"a": 1}' @? '$["a"]'; + ?column? +---------- + t +(1 row) + +select json '{"a": 1}' @? '$["b"]'; + ?column? +---------- + f +(1 row) + +select json '{"a": 1}' @? 'strict $["b"]'; + ?column? +---------- + +(1 row) + +select json '{"a": 1}' @? '$["b", "a"]'; + ?column? +---------- + t +(1 row) + +select json_path_query('{"a": 1}', '$["a"]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('{"a": 1}', 'strict $["b"]'); +ERROR: JSON object does not contain the specified key +select json_path_query('{"a": 1}', 'lax $["b"]'); + json_path_query +----------------- +(0 rows) + +select json_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]'); + json_path_query +------------------ + 2 + 2 + 1 + {"a": 1, "b": 2} +(4 rows) + +select json_path_query('null', '{"a": 1}["a"]'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('null', '{"a": 1}["b"]'); + json_path_query +----------------- +(0 rows) + diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql index 73781b0372..66b71903a4 100644 --- a/src/test/regress/sql/json_jsonpath.sql +++ b/src/test/regress/sql/json_jsonpath.sql @@ -29,6 +29,14 @@ select json '[1]' @? '$[0.5]'; select json '[1]' @? '$[0.9]'; select json '[1]' @? '$[1.2]'; select json '[1]' @? 'strict $[1.2]'; +select json_path_query('[1]', 'strict $[1.2]'); +select json_path_query('{}', 'strict $[0.3]'); +select json '{}' @? 'lax $[0.3]'; +select json_path_query('{}', 'strict $[1.2]'); +select json '{}' @? 'lax $[1.2]'; +select json_path_query('{}', 'strict $[-2 to 3]'); +select json '{}' @? 'lax $[-2 to 3]'; + select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])'; select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])'; @@ -66,6 +74,7 @@ select json_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10 / 0].a'); select json_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]'); select json_path_query('1', 'lax $[0]'); select json_path_query('1', 'lax $[*]'); +select json_path_query('{}', 'lax $[0]'); select json_path_query('[1]', 'lax $[0]'); select json_path_query('[1]', 'lax $[*]'); select json_path_query('[1,2,3]', 'lax $[*]'); @@ -74,6 +83,7 @@ select json_path_query('[]', '$[last]'); select json_path_query('[]', '$[last ? (exists(last))]'); select json_path_query('[]', 'strict $[last]'); select json_path_query('[1]', '$[last]'); +select json_path_query('{}', '$[last]'); select json_path_query('[1,2,3]', '$[last]'); select json_path_query('[1,2,3]', '$[last - 1]'); select json_path_query('[1,2,3]', '$[last ? (@.type() == "number")]'); @@ -120,6 +130,7 @@ select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0 select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)'); select json_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)'); + select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)'; select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)'; select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; @@ -303,7 +314,6 @@ select json_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "adc\nabc", "b select json_path_query('null', '$.datetime()'); select json_path_query('true', '$.datetime()'); -select json_path_query('1', '$.datetime()'); select json_path_query('[]', '$.datetime()'); select json_path_query('[]', 'strict $.datetime()'); select json_path_query('{}', '$.datetime()'); @@ -319,6 +329,11 @@ select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483647)'); select json_path_query('"12:34"', '$.datetime("HH24:MI TZH", -2147483648)'); select json_path_query('"aaaa"', '$.datetime("HH24")'); +-- Standard extension: UNIX epoch to timestamptz +select json_path_query('0', '$.datetime()'); +select json_path_query('0', '$.datetime().type()'); +select json_path_query('1490216035.5', '$.datetime()'); + select json '"10-03-2017"' @? '$.datetime("dd-mm-yyyy")'; select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")'); select json_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); @@ -453,6 +468,7 @@ set time zone default; SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*]'); SELECT json_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)'); +SELECT json_path_query('[{"a": 1}, {"a": 2}]', '[$[*].a]'); SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -460,6 +476,7 @@ SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)'); SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)'); SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}'); SELECT json_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}'); +SELECT json_path_query_array('[{"a": 1}, {"a": 2}]', '[$[*].a]'); SELECT json_path_query_first('[{"a": 1}, {"a": 2}, {}]', 'strict $[*].a'); SELECT json_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a'); @@ -475,3 +492,43 @@ SELECT json_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1'; SELECT json '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2'; + +-- extension: path sequences +select json_path_query('[1,2,3,4,5]', '10, 20, $[*], 30'); +select json_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30'); +select json_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30'); +select json_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)'); +select json_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()'); +select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]'); +select json_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]'); + +-- extension: array constructors +select json_path_query('[1, 2, 3]', '[]'); +select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5]'); +select json_path_query('[1, 2, 3]', '[1, 2, $[*], 4, 5][*]'); +select json_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]'); +select json_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'); +select json_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'); + +-- extension: object constructors +select json_path_query('[1, 2, 3]', '{}'); +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}'); +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*'); +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'); +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}'); +select json_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'); + +-- extension: object subscripting +select json '{"a": 1}' @? '$["a"]'; +select json '{"a": 1}' @? '$["b"]'; +select json '{"a": 1}' @? 'strict $["b"]'; +select json '{"a": 1}' @? '$["b", "a"]'; + +select json_path_query('{"a": 1}', '$["a"]'); +select json_path_query('{"a": 1}', 'strict $["b"]'); +select json_path_query('{"a": 1}', 'lax $["b"]'); +select json_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]'); + +select json_path_query('null', '{"a": 1}["a"]'); +select json_path_query('null', '{"a": 1}["b"]'); From 375e8a006cc4f1a43b54449713a617a6ae88af38 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 28 Feb 2019 20:02:11 +0300 Subject: [PATCH 51/66] Add jsonpath support for double type --- src/backend/executor/execExprInterp.c | 5 + src/backend/parser/parse_expr.c | 1 + src/backend/utils/adt/jsonpath_exec.c | 339 +++++++++++++++---- src/include/executor/execExpr.h | 1 + src/include/nodes/primnodes.h | 1 + src/include/utils/float.h | 78 ++++- src/include/utils/jsonpath.h | 19 +- src/test/regress/expected/json_jsonpath.out | 12 +- src/test/regress/expected/jsonb_jsonpath.out | 12 +- src/test/regress/sql/jsonb_jsonpath.sql | 2 - 10 files changed, 382 insertions(+), 88 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 311e197efe..839f3934aa 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4350,6 +4350,11 @@ ExecPrepareJsonItemCoercion(JsonItem *item, bool is_jsonb, res = JsonItemNumericDatum(item); break; + case jsiDouble: + coercion = &coercions->dbl; + res = JsonItemDoubleDatum(item); + break; + case jbvBool: coercion = &coercions->boolean; res = JsonItemBool(item); diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 421973bb9a..f87458bd1f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4754,6 +4754,7 @@ initJsonItemCoercions(ParseState *pstate, JsonItemCoercions *coercions, { &coercions->null, UNKNOWNOID }, { &coercions->string, TEXTOID }, { &coercions->numeric, NUMERICOID }, + { &coercions->dbl, FLOAT8OID }, { &coercions->boolean, BOOLOID }, { &coercions->date, DATEOID }, { &coercions->time, TIMEOID }, diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0de29663e7..b294c44f95 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -284,12 +284,16 @@ typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp, JsonItem *larg, JsonItem *rarg, void *param); -typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error); + +typedef Numeric (*BinaryNumericFunc) (Numeric num1, Numeric num2, bool *error); +typedef float8 (*BinaryDoubleFunc) (float8 num1, float8 num2, bool *error); typedef JsonbValue *(*JsonBuilderFunc) (JsonbParseState **, JsonbIteratorToken, JsonbValue *); +static float8 float8_mod_error(float8 val1, float8 val2, bool *error); + static void freeUserFuncContext(JsonPathUserFuncContext *cxt); static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, bool isJsonb, bool copy); @@ -342,11 +346,14 @@ static JsonPathBool executePredicate(JsonPathExecContext *cxt, static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, - BinaryArithmFunc func, + BinaryNumericFunc numFunc, + BinaryDoubleFunc dblFunc, JsonValueList *found); static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonItem *jb, PGFunction func, + JsonItem *jb, + PGFunction numFunc, + PGFunction dblFunc, JsonValueList *found); static JsonPathBool executeStartsWith(JsonPathItem *jsp, JsonItem *whole, JsonItem *initial, void *param); @@ -355,7 +362,8 @@ static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonItem *str, static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, bool unwrap, - PGFunction func, + PGFunction numericFunc, + PGFunction doubleFunc, JsonValueList *found); static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, @@ -380,6 +388,7 @@ static void JsonItemInitBool(JsonItem *item, bool val); static void JsonItemInitNumeric(JsonItem *item, Numeric val); #define JsonItemInitNumericDatum(item, val) \ JsonItemInitNumeric(item, DatumGetNumeric(val)) +static void JsonItemInitDouble(JsonItem *item, float8 val); static void JsonItemInitString(JsonItem *item, char *str, int len); static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz); @@ -407,6 +416,8 @@ static int JsonbType(JsonItem *jb); static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); static inline JsonbValue *JsonInitBinary(JsonbValue *jbv, Json *js); static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); +static JsonItem *getNumber(JsonItem *scalar); +static bool convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num); static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, @@ -1307,30 +1318,35 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAdd: return executeBinaryArithmExpr(cxt, jsp, jb, - numeric_add_opt_error, found); + numeric_add_opt_error, + float8_pl_error, found); case jpiSub: return executeBinaryArithmExpr(cxt, jsp, jb, - numeric_sub_opt_error, found); + numeric_sub_opt_error, + float8_mi_error, found); case jpiMul: return executeBinaryArithmExpr(cxt, jsp, jb, - numeric_mul_opt_error, found); + numeric_mul_opt_error, + float8_mul_error, found); case jpiDiv: return executeBinaryArithmExpr(cxt, jsp, jb, - numeric_div_opt_error, found); + numeric_div_opt_error, + float8_div_error, found); case jpiMod: return executeBinaryArithmExpr(cxt, jsp, jb, - numeric_mod_opt_error, found); + numeric_mod_opt_error, + float8_mod_error, found); case jpiPlus: - return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found); + return executeUnaryArithmExpr(cxt, jsp, jb, NULL, NULL, found); case jpiMinus: return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus, - found); + float8um, found); case jpiFilter: { @@ -1450,15 +1466,15 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAbs: return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_abs, - found); + float8abs, found); case jpiFloor: return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_floor, - found); + dfloor, found); case jpiCeiling: return executeNumericItemMethod(cxt, jsp, jb, unwrap, numeric_ceil, - found); + dceil, found); case jpiDouble: { @@ -1468,29 +1484,36 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); - if (JsonItemIsNumeric(jb)) + if (JsonItemIsDouble(jb)) + { + /* nothing to do */ + } + else if (JsonItemIsNumeric(jb)) { char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, JsonItemNumericDatum(jb))); bool have_error = false; + float8 val; - (void) float8in_internal_opt_error(tmp, - NULL, - "double precision", - tmp, - &have_error); + val = float8in_internal_opt_error(tmp, + NULL, + "double precision", + tmp, + &have_error); if (have_error) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM), errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); - res = jperOk; + + jb = &jsi; + JsonItemInitDouble(jb, val); } else if (JsonItemIsString(jb)) { /* cast string as double */ - double val; + float8 val; char *tmp = pnstrdup(JsonItemString(jb).val, JsonItemString(jb).len); bool have_error = false; @@ -1501,19 +1524,16 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, tmp, &have_error); - if (have_error || isinf(val)) + if (have_error) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM), errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); jb = &jsi; - JsonItemInitNumericDatum(jb, DirectFunctionCall1(float8_numeric, - Float8GetDatum(val))); - res = jperOk; + JsonItemInitDouble(jb, val); } - - if (res == jperNotFound) + else RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM), errmsg("jsonpath item method .%s() can only be applied to a string or numeric value", @@ -1538,14 +1558,26 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); - if (JsonItemIsNumeric(jb)) + if (JsonItemIsNumber(jb)) { bool error = false; - float8 unix_epoch = - DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow, - JsonItemNumericDatum(jb))); - TimestampTz tstz = float8_timestamptz_internal(unix_epoch, - &error); + float8 unix_epoch; + TimestampTz tstz; + + if (JsonItemIsNumeric(jb)) + { + Datum flt = DirectFunctionCall1(numeric_float8_no_overflow, + JsonItemNumericDatum(jb)); + + unix_epoch = DatumGetFloat8(flt); + } + else + { + Assert(JsonItemIsDouble(jb)); + unix_epoch = JsonItemDouble(jb); + } + + tstz = float8_timestamptz_internal(unix_epoch, &error); if (error) RETURN_ERROR(ereport(ERROR, @@ -2323,14 +2355,39 @@ executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, return jpbFalse; } +static float8 +float8_mod_error(float8 val1, float8 val2, bool *error) +{ + float8 res; + + if (error) + { + res = float8_div_error(val1, val2, error); + if (!*error) + { + res = float8_mul_error(trunc(res), val1, error); + if (!*error) + res = float8_mi_error(val1, res, error); + } + } + else + { + res = float8_div(val1, val2); + res = float8_mul(trunc(res), val1); + res = float8_mi(val1, res); + } + + return res; +} + /* * Execute binary arithmetic expression on singleton numeric operands. * Array operands are automatically unwrapped in lax mode. */ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonItem *jb, BinaryArithmFunc func, - JsonValueList *found) + JsonItem *jb, BinaryNumericFunc numFunc, + BinaryDoubleFunc dblFunc, JsonValueList *found) { JsonPathExecResult jper; JsonPathItem elem; @@ -2357,28 +2414,71 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return jper; if (JsonValueListLength(&lseq) != 1 || - !(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric))) + !(lval = getNumber(JsonValueListHead(&lseq)))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), errmsg("left operand of jsonpath operator %s is not a single numeric value", jspOperationName(jsp->type))))); if (JsonValueListLength(&rseq) != 1 || - !(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric))) + !(rval = getNumber(JsonValueListHead(&rseq)))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), errmsg("right operand of jsonpath operator %s is not a single numeric value", jspOperationName(jsp->type))))); + if (JsonItemIsDouble(lval) && JsonItemIsDouble(rval)) + { + float8 ld = JsonItemDouble(lval); + float8 rd = JsonItemDouble(rval); + float8 r; + + if (jspThrowErrors(cxt)) + { + r = dblFunc(ld, rd, NULL); + } + else + { + bool error = false; + + r = dblFunc(ld, rd, &error); + + if (error) + return jperError; + } + + if (!jspGetNext(jsp, &elem) && !found) + return jperOk; + + lval = palloc(sizeof(*lval)); + JsonItemInitDouble(lval, r); + + return executeNextItem(cxt, jsp, &elem, lval, found, false); + } + else if (JsonItemIsDouble(lval)) + { + if (!convertJsonDoubleToNumeric(lval, lval)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to numeric")))); + } + else if (JsonItemIsDouble(rval)) + { + if (!convertJsonDoubleToNumeric(rval, rval)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to numeric")))); + } + if (jspThrowErrors(cxt)) { - res = func(JsonItemNumeric(lval), JsonItemNumeric(rval), NULL); + res = numFunc(JsonItemNumeric(lval), JsonItemNumeric(rval), NULL); } else { bool error = false; - res = func(JsonItemNumeric(lval), JsonItemNumeric(rval), &error); + res = numFunc(JsonItemNumeric(lval), JsonItemNumeric(rval), &error); if (error) return jperError; @@ -2399,7 +2499,8 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonItem *jb, PGFunction func, JsonValueList *found) + JsonItem *jb, PGFunction numFunc, PGFunction dblFunc, + JsonValueList *found) { JsonPathExecResult jper; JsonPathExecResult jper2; @@ -2422,7 +2523,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonValueListInitIterator(&seq, &it); while ((val = JsonValueListNext(&seq, &it))) { - if ((val = getScalar(val, jbvNumeric))) + if ((val = getNumber(val))) { if (!found && !hasNext) return jperOk; @@ -2438,10 +2539,21 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jspOperationName(jsp->type))))); } - if (func) - JsonItemNumeric(val) = - DatumGetNumeric(DirectFunctionCall1(func, - NumericGetDatum(JsonItemNumeric(val)))); + if (JsonItemIsNumeric(val)) + { + if (numFunc) + JsonItemNumeric(val) = + DatumGetNumeric(DirectFunctionCall1(numFunc, + JsonItemNumericDatum(val))); + } + else + { + Assert(JsonItemIsDouble(val)); + if (dblFunc) + JsonItemDouble(val) = + DatumGetFloat8(DirectFunctionCall1(dblFunc, + JsonItemDoubleDatum(val))); + } jper2 = executeNextItem(cxt, jsp, &elem, val, found, false); @@ -2539,30 +2651,41 @@ executeLikeRegex(JsonPathItem *jsp, JsonItem *str, JsonItem *rarg, */ static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonItem *jb, bool unwrap, PGFunction func, - JsonValueList *found) + JsonItem *jb, bool unwrap, PGFunction numericFunc, + PGFunction doubleFunc, JsonValueList *found) { JsonPathItem next; + JsonItem res; Datum datum; if (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); - if (!(jb = getScalar(jb, jbvNumeric))) + if (!(jb = getNumber(jb))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM), errmsg("jsonpath item method .%s() can only be applied to a numeric value", jspOperationName(jsp->type))))); - datum = DirectFunctionCall1(func, JsonItemNumericDatum(jb)); + if (JsonItemIsNumeric(jb)) + { + datum = DirectFunctionCall1(numericFunc, JsonItemNumericDatum(jb)); + } + else + { + Assert(JsonItemIsDouble(jb)); + datum = DirectFunctionCall1(doubleFunc, JsonItemDoubleDatum(jb)); + } if (!jspGetNext(jsp, &next) && !found) return jperOk; - jb = palloc(sizeof(*jb)); - JsonItemInitNumericDatum(jb, datum); + if (JsonItemIsNumeric(jb)) + JsonItemInitNumericDatum(&res, datum); + else + JsonItemInitDouble(&res, DatumGetFloat8(datum)); - return executeNextItem(cxt, jsp, &next, jb, found, false); + return executeNextItem(cxt, jsp, &next, &res, found, true); } /* @@ -2899,6 +3022,7 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) { JsonbValue *jb1 = JsonItemJbv(jsi1); JsonbValue *jb2 = JsonItemJbv(jsi2); + JsonItem jsibuf; int cmp; bool res; @@ -2912,8 +3036,24 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) */ return op == jpiNotEqual ? jpbTrue : jpbFalse; - /* Non-null items of different types are not comparable. */ - return jpbUnknown; + if (!JsonItemIsNumber(jsi1) || !JsonItemIsNumber(jsi2)) + /* Non-null items of different not-number types are not comparable. */ + return jpbUnknown; + + if (JsonItemIsDouble(jsi1)) + { + if (!convertJsonDoubleToNumeric(jsi1, &jsibuf)) + return jpbUnknown; + jsi1 = &jsibuf; + jb1 = JsonItemJbv(jsi1); + } + else if (JsonItemIsDouble(jsi2)) + { + if (!convertJsonDoubleToNumeric(jsi2, &jsibuf)) + return jpbUnknown; + jsi2 = &jsibuf; + jb2 = JsonItemJbv(jsi2); + } } switch (JsonItemGetType(jsi1)) @@ -2928,6 +3068,10 @@ compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) case jbvNumeric: cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric); break; + case jsiDouble: + cmp = float8_cmp_internal(JsonItemDouble(jsi1), + JsonItemDouble(jsi2)); + break; case jbvString: if (op == jpiEqual) return jb1->val.string.len != jb2->val.string.len || @@ -3033,6 +3177,26 @@ JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv) jbv->val.string.len = strlen(jbv->val.string.val); return jbv; + case jsiDouble: + { + float8 val = JsonItemDouble(jsi); + + if (isinf(val)) /* numeric can be NaN but not Inf */ + { + jbv->type = jbvString; + jbv->val.string.val = float8out_internal(val); + jbv->val.string.len = strlen(jbv->val.string.val); + } + else + { + jbv->type = jbvNumeric; + jbv->val.numeric = + DatumGetNumeric(DirectFunctionCall1(float8_numeric, + Float8GetDatum(val))); + } + return jbv; + } + default: return JsonItemJbv(jsi); } @@ -3093,17 +3257,35 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, return res; if (JsonValueListLength(&found) != 1 || - !(jbv = getScalar(JsonValueListHead(&found), jbvNumeric))) + !(jbv = getNumber(JsonValueListHead(&found)))) RETURN_ERROR(ereport(ERROR, (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT), errmsg("jsonpath array subscript is not a single numeric value")))); - numeric_index = DirectFunctionCall2(numeric_trunc, - NumericGetDatum(JsonItemNumeric(jbv)), - Int32GetDatum(0)); + if (JsonItemIsNumeric(jbv)) + { + numeric_index = DirectFunctionCall2(numeric_trunc, + JsonItemNumericDatum(jbv), + Int32GetDatum(0)); + + *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index), + &have_error); + } + else + { + float8 val; + + Assert(JsonItemIsDouble(jbv)); - *index = numeric_int4_opt_error(DatumGetNumeric(numeric_index), - &have_error); + val = floor(JsonItemDouble(jbv)); + + if (unlikely(val < (float8) PG_INT32_MIN || + val >= -((float8) PG_INT32_MIN) || + isnan(val))) + have_error = true; + else + *index = (int32) val; + } if (have_error) RETURN_ERROR(ereport(ERROR, @@ -3347,6 +3529,9 @@ JsonItemUnquote(JsonItem *jsi, int *len, bool isJsonb) JsonItemDatetime(jsi).value, JsonItemDatetime(jsi).typid, &JsonItemDatetime(jsi).tz); + case jsiDouble: + *len = -1; + return float8out_internal(JsonItemDouble(jsi)); default: return JsonbValueUnquote(JsonItemJbv(jsi), len, isJsonb); @@ -3456,6 +3641,27 @@ getScalar(JsonItem *scalar, enum jbvType type) return JsonItemGetType(scalar) == type ? scalar : NULL; } +static JsonItem * +getNumber(JsonItem *scalar) +{ + /* Scalars should be always extracted during jsonpath execution. */ + Assert(!JsonItemIsBinary(scalar) || + !JsonContainerIsScalar(JsonItemBinary(scalar).data)); + + return JsonItemIsNumber(scalar) ? scalar : NULL; +} + +bool +convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num) +{ + if (isinf(JsonItemDouble(dbl))) + return false; + + JsonItemInitNumericDatum(num, DirectFunctionCall1(float8_numeric, + JsonItemDoubleDatum(dbl))); + return true; +} + /* Construct a JSON array from the item list */ static JsonbValue * wrapItemsInArray(const JsonValueList *items, bool isJsonb) @@ -3774,6 +3980,13 @@ JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz) JsonItemDatetime(item).tz = tz; } +static void +JsonItemInitDouble(JsonItem *item, double val) +{ + item->val.type = jsiDouble; + JsonItemDouble(item) = val; +} + /********************Interface to pgsql's executor***************************/ bool @@ -3961,10 +4174,10 @@ JsonItemFromDatum(Datum val, Oid typid, int32 typmod, JsonItem *res, JsonItemInitNumericDatum(res, DirectFunctionCall1(int8_numeric, val)); break; case FLOAT4OID: - JsonItemInitNumericDatum(res, DirectFunctionCall1(float4_numeric, val)); + JsonItemInitDouble(res, DatumGetFloat4(val)); break; case FLOAT8OID: - JsonItemInitNumericDatum(res, DirectFunctionCall1(float8_numeric, val)); + JsonItemInitDouble(res, DatumGetFloat8(val)); break; case TEXTOID: case VARCHAROID: diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 179d127fef..5eec3b1301 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -694,6 +694,7 @@ typedef struct ExprEvalStep } null, string, numeric, + dbl, boolean, date, time, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 2eabccae07..ecb3e833b7 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1331,6 +1331,7 @@ typedef struct JsonItemCoercions JsonCoercion *null; JsonCoercion *string; JsonCoercion *numeric; + JsonCoercion *dbl; JsonCoercion *boolean; JsonCoercion *date; JsonCoercion *time; diff --git a/src/include/utils/float.h b/src/include/utils/float.h index 543d00e591..cb311e2119 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -148,18 +148,41 @@ check_float4_val(const float4 val, const bool inf_is_valid, } static inline void -check_float8_val(const float8 val, const bool inf_is_valid, - const bool zero_is_valid) +check_float8_val_error(const float8 val, const bool inf_is_valid, + const bool zero_is_valid, bool *error) { if (!inf_is_valid && unlikely(isinf(val))) + { + if (error) + { + *error = true; + return; + } + ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: overflow"))); + } if (!zero_is_valid && unlikely(val == 0.0)) + { + if (error) + { + *error = true; + return; + } + ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("value out of range: underflow"))); + } +} + +static inline void +check_float8_val(const float8 val, const bool inf_is_valid, + const bool zero_is_valid) +{ + check_float8_val_error(val, inf_is_valid, zero_is_valid, NULL); } /* @@ -184,16 +207,22 @@ float4_pl(const float4 val1, const float4 val2) } static inline float8 -float8_pl(const float8 val1, const float8 val2) +float8_pl_error(const float8 val1, const float8 val2, bool *error) { float8 result; result = val1 + val2; - check_float8_val(result, isinf(val1) || isinf(val2), true); + check_float8_val_error(result, isinf(val1) || isinf(val2), true, error); return result; } +static inline float8 +float8_pl(const float8 val1, const float8 val2) +{ + return float8_pl_error(val1, val2, NULL); +} + static inline float4 float4_mi(const float4 val1, const float4 val2) { @@ -206,16 +235,22 @@ float4_mi(const float4 val1, const float4 val2) } static inline float8 -float8_mi(const float8 val1, const float8 val2) +float8_mi_error(const float8 val1, const float8 val2, bool *error) { float8 result; result = val1 - val2; - check_float8_val(result, isinf(val1) || isinf(val2), true); + check_float8_val_error(result, isinf(val1) || isinf(val2), true, error); return result; } +static inline float8 +float8_mi(const float8 val1, const float8 val2) +{ + return float8_mi_error(val1, val2, NULL); +} + static inline float4 float4_mul(const float4 val1, const float4 val2) { @@ -229,17 +264,23 @@ float4_mul(const float4 val1, const float4 val2) } static inline float8 -float8_mul(const float8 val1, const float8 val2) +float8_mul_error(const float8 val1, const float8 val2, bool *error) { float8 result; result = val1 * val2; - check_float8_val(result, isinf(val1) || isinf(val2), - val1 == 0.0 || val2 == 0.0); + check_float8_val_error(result, isinf(val1) || isinf(val2), + val1 == 0.0 || val2 == 0.0, error); return result; } +static inline float8 +float8_mul(const float8 val1, const float8 val2) +{ + return float8_mul_error(val1, val2, NULL); +} + static inline float4 float4_div(const float4 val1, const float4 val2) { @@ -257,21 +298,36 @@ float4_div(const float4 val1, const float4 val2) } static inline float8 -float8_div(const float8 val1, const float8 val2) +float8_div_error(const float8 val1, const float8 val2, bool *error) { float8 result; if (val2 == 0.0) + { + if (error) + { + *error = true; + return 0.0; + } + ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + } result = val1 / val2; - check_float8_val(result, isinf(val1) || isinf(val2), val1 == 0.0); + check_float8_val_error(result, isinf(val1) || isinf(val2), val1 == 0.0, + error); return result; } +static inline float8 +float8_div(const float8 val1, const float8 val2) +{ + return float8_div_error(val1, val2, NULL); +} + /* * Routines for NaN-aware comparisons * diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index cc39f8766b..8f29ab7848 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -317,9 +317,10 @@ typedef enum JsonItemType * Virtual types. * * These types are used only for in-memory JSON processing and serialized - * into JSON strings when outputted to json/jsonb. + * into JSON strings/numbers when outputted to json/jsonb. */ - jsiDatetime = 0x20 + jsiDatetime = 0x20, + jsiDouble = 0x21 } JsonItemType; /* SQL/JSON item */ @@ -341,6 +342,12 @@ typedef struct JsonItem int32 typmod; int tz; } datetime; + + struct + { + int type; + double val; + } dbl; } val; } JsonItem; @@ -353,6 +360,8 @@ typedef struct JsonItem #define JsonItemArray(jsi) (JsonItemJbv(jsi)->val.array) #define JsonItemObject(jsi) (JsonItemJbv(jsi)->val.object) #define JsonItemDatetime(jsi) ((jsi)->val.datetime) +#define JsonItemDouble(jsi) ((jsi)->val.dbl.val) +#define JsonItemDoubleDatum(jsi) Float8GetDatum(JsonItemDouble(jsi)) #define JsonItemGetType(jsi) ((jsi)->val.type) #define JsonItemIsNull(jsi) (JsonItemGetType(jsi) == jsiNull) @@ -363,8 +372,12 @@ typedef struct JsonItem #define JsonItemIsArray(jsi) (JsonItemGetType(jsi) == jsiArray) #define JsonItemIsObject(jsi) (JsonItemGetType(jsi) == jsiObject) #define JsonItemIsDatetime(jsi) (JsonItemGetType(jsi) == jsiDatetime) +#define JsonItemIsDouble(jsi) (JsonItemGetType(jsi) == jsiDouble) #define JsonItemIsScalar(jsi) (IsAJsonbScalar(JsonItemJbv(jsi)) || \ - JsonItemIsDatetime(jsi)) + JsonItemIsDatetime(jsi) || \ + JsonItemIsDouble(jsi)) +#define JsonItemIsNumber(jsi) (JsonItemIsNumeric(jsi) || \ + JsonItemIsDouble(jsi)) extern Jsonb *JsonItemToJsonb(JsonItem *jsi); extern Json *JsonItemToJson(JsonItem *jsi); diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index 918e109b0a..a35cf33567 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -1388,9 +1388,17 @@ select json_path_query('"NaN"', '$.double()'); (1 row) select json_path_query('"inf"', '$.double()'); -ERROR: jsonpath item method .double() can only be applied to a numeric value + json_path_query +----------------- + "Infinity" +(1 row) + select json_path_query('"-inf"', '$.double()'); -ERROR: jsonpath item method .double() can only be applied to a numeric value + json_path_query +----------------- + "-Infinity" +(1 row) + select json_path_query('{}', '$.abs()'); ERROR: jsonpath item method .abs() can only be applied to a numeric value select json_path_query('true', '$.floor()'); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 2ea0cb8687..14cea9901a 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1548,18 +1548,16 @@ select jsonb_path_query('"NaN"', '$.double()'); (1 row) select jsonb_path_query('"inf"', '$.double()'); -ERROR: jsonpath item method .double() can only be applied to a numeric value -select jsonb_path_query('"-inf"', '$.double()'); -ERROR: jsonpath item method .double() can only be applied to a numeric value -select jsonb_path_query('"inf"', '$.double()', silent => true); jsonb_path_query ------------------ -(0 rows) + "Infinity" +(1 row) -select jsonb_path_query('"-inf"', '$.double()', silent => true); +select jsonb_path_query('"-inf"', '$.double()'); jsonb_path_query ------------------ -(0 rows) + "-Infinity" +(1 row) select jsonb_path_query('{}', '$.abs()'); ERROR: jsonpath item method .abs() can only be applied to a numeric value diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 474b325751..9f0a66ef92 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -327,8 +327,6 @@ select jsonb_path_query('"nan"', '$.double()'); select jsonb_path_query('"NaN"', '$.double()'); select jsonb_path_query('"inf"', '$.double()'); select jsonb_path_query('"-inf"', '$.double()'); -select jsonb_path_query('"inf"', '$.double()', silent => true); -select jsonb_path_query('"-inf"', '$.double()', silent => true); select jsonb_path_query('{}', '$.abs()'); select jsonb_path_query('true', '$.floor()'); From c527c29100b00e36b8f7052560b4e4f3a7f7fa09 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sun, 3 Feb 2019 01:30:21 +0300 Subject: [PATCH 52/66] WIP: optimize jsonb --- src/backend/utils/adt/json.c | 45 +++-- src/backend/utils/adt/jsonb.c | 9 +- src/backend/utils/adt/jsonb_op.c | 9 +- src/backend/utils/adt/jsonb_util.c | 202 +++++++++++++--------- src/backend/utils/adt/jsonfuncs.c | 239 +++++++++----------------- src/backend/utils/adt/jsonpath_exec.c | 46 ++--- src/include/utils/jsonapi.h | 6 +- src/include/utils/jsonb.h | 10 +- 8 files changed, 266 insertions(+), 300 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 0e7e18a4bf..468d6a87c2 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -3713,41 +3713,44 @@ JsonGetArraySize(JsonContainer *jc) * Find last key in a json object by name. Returns palloc()'d copy of the * corresponding value, or NULL if is not found. */ -static inline JsonbValue * -jsonFindLastKeyInObject(JsonContainer *obj, const JsonbValue *key) +JsonbValue * +jsonFindLastKeyInObject(JsonContainer *obj, const char *keyval, int keylen, + JsonbValue *res) { - JsonbValue *res = NULL; + JsonbValue *val = NULL; JsonbValue jbv; JsonIterator *it; JsonbIteratorToken tok; Assert(JsonContainerIsObject(obj)); - Assert(key->type == jbvString); it = JsonIteratorInit(obj); while ((tok = JsonIteratorNext(&it, &jbv, true)) != WJB_DONE) { - if (tok == WJB_KEY && !lengthCompareJsonbStringValue(key, &jbv)) + if (tok == WJB_KEY && + jbv.val.string.len == keylen && + !memcmp(jbv.val.string.val, keyval, keylen)) { - if (!res) - res = palloc(sizeof(*res)); + if (!val) + val = res ? res : palloc(sizeof(*val)); tok = JsonIteratorNext(&it, res, true); Assert(tok == WJB_VALUE); } } - return res; + return val; } /* * Find scalar element in a array. Returns palloc()'d copy of value or NULL. */ static JsonbValue * -jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem) +jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem, + JsonbValue *res) { - JsonbValue *val = palloc(sizeof(*val)); + JsonbValue *val = res ? res : palloc(sizeof(*val)); JsonIterator *it; JsonbIteratorToken tok; @@ -3766,7 +3769,8 @@ jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem) } } - pfree(val); + if (!res) + pfree(val); return NULL; } @@ -3780,7 +3784,8 @@ jsonFindValueInArray(JsonContainer *array, const JsonbValue *elem) * For more details, see findJsonbValueFromContainer(). */ JsonbValue * -findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key) +findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key, + JsonbValue *val) { Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0); @@ -3788,10 +3793,14 @@ findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key) return NULL; if ((flags & JB_FARRAY) && JsonContainerIsArray(jc)) - return jsonFindValueInArray(jc, key); + return jsonFindValueInArray(jc, key, val); if ((flags & JB_FOBJECT) && JsonContainerIsObject(jc)) - return jsonFindLastKeyInObject(jc, key); + { + Assert(key->type == jbvString); + return jsonFindLastKeyInObject(jc, key->val.string.val, + key->val.string.len, val); + } /* Not found */ return NULL; @@ -3803,9 +3812,10 @@ findJsonValueFromContainer(JsonContainer *jc, uint32 flags, JsonbValue *key) * Returns palloc()'d copy of the value, or NULL if it does not exist. */ JsonbValue * -getIthJsonValueFromContainer(JsonContainer *array, uint32 index) +getIthJsonValueFromContainer(JsonContainer *array, uint32 index, + JsonbValue *res) { - JsonbValue *val = palloc(sizeof(JsonbValue)); + JsonbValue *val = res ? res : palloc(sizeof(*val)); JsonIterator *it; JsonbIteratorToken tok; @@ -3825,7 +3835,8 @@ getIthJsonValueFromContainer(JsonContainer *array, uint32 index) } } - pfree(val); + if (!res) + pfree(val); return NULL; } diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 686a44aeb2..cd68e9bdd7 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2161,9 +2161,12 @@ jsonb_is_valid(PG_FUNCTION_ARGS) bool JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) { +#if 0 JsonbIterator *it; JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY; JsonbValue tmp; +#endif + JsonbValue *scalar PG_USED_FOR_ASSERTS_ONLY; if (!JsonContainerIsArray(jbc) || !JsonContainerIsScalar(jbc)) { @@ -2172,6 +2175,10 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) return false; } +#if 1 + scalar = getIthJsonbValueFromContainer(jbc, 0, res); + Assert(scalar); +#else /* * A root scalar is stored as an array of one element, so we get the array * and then its first (and only) member. @@ -2191,7 +2198,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) tok = JsonbIteratorNext(&it, &tmp, true); Assert(tok == WJB_DONE); - +#endif return true; } diff --git a/src/backend/utils/adt/jsonb_op.c b/src/backend/utils/adt/jsonb_op.c index a64206eeb1..82c4b0b2cb 100644 --- a/src/backend/utils/adt/jsonb_op.c +++ b/src/backend/utils/adt/jsonb_op.c @@ -24,6 +24,7 @@ jsonb_exists(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); text *key = PG_GETARG_TEXT_PP(1); JsonbValue kval; + JsonbValue vval; JsonbValue *v = NULL; /* @@ -38,7 +39,7 @@ jsonb_exists(PG_FUNCTION_ARGS) v = findJsonbValueFromContainer(&jb->root, JB_FOBJECT | JB_FARRAY, - &kval); + &kval, &vval); PG_RETURN_BOOL(v != NULL); } @@ -59,6 +60,7 @@ jsonb_exists_any(PG_FUNCTION_ARGS) for (i = 0; i < elem_count; i++) { JsonbValue strVal; + JsonbValue valVal; if (key_nulls[i]) continue; @@ -69,7 +71,7 @@ jsonb_exists_any(PG_FUNCTION_ARGS) if (findJsonbValueFromContainer(&jb->root, JB_FOBJECT | JB_FARRAY, - &strVal) != NULL) + &strVal, &valVal) != NULL) PG_RETURN_BOOL(true); } @@ -92,6 +94,7 @@ jsonb_exists_all(PG_FUNCTION_ARGS) for (i = 0; i < elem_count; i++) { JsonbValue strVal; + JsonbValue valVal; if (key_nulls[i]) continue; @@ -102,7 +105,7 @@ jsonb_exists_all(PG_FUNCTION_ARGS) if (findJsonbValueFromContainer(&jb->root, JB_FOBJECT | JB_FARRAY, - &strVal) == NULL) + &strVal, &valVal) == NULL) PG_RETURN_BOOL(false); } diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 8de67dab35..4d5d9f09e9 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -57,6 +57,8 @@ static void appendKey(JsonbParseState *pstate, JsonbValue *scalarVal); static void appendValue(JsonbParseState *pstate, JsonbValue *scalarVal); static void appendElement(JsonbParseState *pstate, JsonbValue *scalarVal); static int lengthCompareJsonbPair(const void *a, const void *b, void *arg); +static int lengthCompareJsonbString(const char *val1, int len1, + const char *val2, int len2); static void uniqueifyJsonbObject(JsonbValue *object); /* @@ -294,6 +296,95 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) return res; } +static bool +jsonbFindElementInArray(JsonbContainer *container, JsonbValue *key) +{ + JEntry *children = container->children; + JsonbValue result; + int count = JsonContainerSize(container); + char *base_addr = (char *) (children + count); + uint32 offset = 0; + int i; + + for (i = 0; i < count; i++) + { + fillJsonbValue(container, i, base_addr, offset, &result); + + if (key->type == result.type && + equalsJsonbScalarValue(key, &result)) + return true; + + JBE_ADVANCE_OFFSET(offset, children[i]); + } + + return false; +} + +JsonbValue * +jsonbFindKeyInObject(JsonbContainer *container, const char *keyVal, int keyLen, + JsonbValue *res) +{ + JEntry *children = container->children; + JsonbValue *result = res; + int count = JsonContainerSize(container); + /* Since this is an object, account for *Pairs* of Jentrys */ + char *base_addr = (char *) (children + count * 2); + uint32 stopLow = 0, + stopHigh = count; + + Assert(JsonContainerIsObject(container)); + + /* Quick out without a palloc cycle if object is empty */ + if (count <= 0) + return NULL; + + if (!result) + result = palloc(sizeof(JsonbValue)); + + /* Binary search on object/pair keys *only* */ + while (stopLow < stopHigh) + { + uint32 stopMiddle; + int difference; + const char *candidateVal; + int candidateLen; + + stopMiddle = stopLow + (stopHigh - stopLow) / 2; + + candidateVal = base_addr + getJsonbOffset(container, stopMiddle); + candidateLen = getJsonbLength(container, stopMiddle); + + difference = lengthCompareJsonbString(candidateVal, candidateLen, + keyVal, keyLen); + + if (difference == 0) + { + /* Found our key, return corresponding value */ + int index = stopMiddle + count; + + fillJsonbValue(container, index, base_addr, + getJsonbOffset(container, index), + result); + + return result; + } + else + { + if (difference < 0) + stopLow = stopMiddle + 1; + else + stopHigh = stopMiddle; + } + } + + /* Not found */ + if (!res) + pfree(result); + + return NULL; +} + + /* * Find value in object (i.e. the "value" part of some key/value pair in an * object), or find a matching element if we're looking through an array. Do @@ -322,88 +413,31 @@ compareJsonbContainers(JsonbContainer *a, JsonbContainer *b) */ JsonbValue * findJsonbValueFromContainer(JsonbContainer *container, uint32 flags, - JsonbValue *key) + JsonbValue *key, JsonbValue *res) { - JEntry *children = container->children; int count = JsonContainerSize(container); - JsonbValue *result; Assert((flags & ~(JB_FARRAY | JB_FOBJECT)) == 0); - /* Quick out without a palloc cycle if object/array is empty */ + /* Quick out if object/array is empty */ if (count <= 0) return NULL; - result = palloc(sizeof(JsonbValue)); - if ((flags & JB_FARRAY) && JsonContainerIsArray(container)) { - char *base_addr = (char *) (children + count); - uint32 offset = 0; - int i; - - for (i = 0; i < count; i++) - { - fillJsonbValue(container, i, base_addr, offset, result); - - if (key->type == result->type) - { - if (equalsJsonbScalarValue(key, result)) - return result; - } - - JBE_ADVANCE_OFFSET(offset, children[i]); - } + if (jsonbFindElementInArray(container, key)) + return key; } else if ((flags & JB_FOBJECT) && JsonContainerIsObject(container)) { - /* Since this is an object, account for *Pairs* of Jentrys */ - char *base_addr = (char *) (children + count * 2); - uint32 stopLow = 0, - stopHigh = count; - /* Object key passed by caller must be a string */ Assert(key->type == jbvString); - /* Binary search on object/pair keys *only* */ - while (stopLow < stopHigh) - { - uint32 stopMiddle; - int difference; - JsonbValue candidate; - - stopMiddle = stopLow + (stopHigh - stopLow) / 2; - - candidate.type = jbvString; - candidate.val.string.val = - base_addr + getJsonbOffset(container, stopMiddle); - candidate.val.string.len = getJsonbLength(container, stopMiddle); - - difference = lengthCompareJsonbStringValue(&candidate, key); - - if (difference == 0) - { - /* Found our key, return corresponding value */ - int index = stopMiddle + count; - - fillJsonbValue(container, index, base_addr, - getJsonbOffset(container, index), - result); - - return result; - } - else - { - if (difference < 0) - stopLow = stopMiddle + 1; - else - stopHigh = stopMiddle; - } - } + return jsonbFindKeyInObject(container, key->val.string.val, + key->val.string.len, res); } /* Not found */ - pfree(result); return NULL; } @@ -413,9 +447,9 @@ findJsonbValueFromContainer(JsonbContainer *container, uint32 flags, * Returns palloc()'d copy of the value, or NULL if it does not exist. */ JsonbValue * -getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) +getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i, + JsonbValue *result) { - JsonbValue *result; char *base_addr; uint32 nelements; @@ -428,7 +462,8 @@ getIthJsonbValueFromContainer(JsonbContainer *container, uint32 i) if (i >= nelements) return NULL; - result = palloc(sizeof(JsonbValue)); + if (!result) + result = palloc(sizeof(JsonbValue)); fillJsonbValue(container, i, base_addr, getJsonbOffset(container, i), @@ -1008,6 +1043,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) for (;;) { JsonbValue *lhsVal; /* lhsVal is from pair in lhs object */ + JsonbValue lhsValBuf; rcont = JsonbIteratorNext(mContained, &vcontained, false); @@ -1020,11 +1056,13 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) return true; Assert(rcont == WJB_KEY); + Assert(vcontained.type == jbvString); /* First, find value by key... */ - lhsVal = findJsonbValueFromContainer((*val)->container, - JB_FOBJECT, - &vcontained); + lhsVal = jsonbFindKeyInObject((*val)->container, + vcontained.val.string.val, + vcontained.val.string.len, + &lhsValBuf); if (!lhsVal) return false; @@ -1125,9 +1163,7 @@ JsonbDeepContains(JsonbIterator **val, JsonbIterator **mContained) if (IsAJsonbScalar(&vcontained)) { - if (!findJsonbValueFromContainer((*val)->container, - JB_FARRAY, - &vcontained)) + if (!jsonbFindElementInArray((*val)->container, &vcontained)) return false; } else @@ -1753,6 +1789,15 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) } } +static int +lengthCompareJsonbString(const char *val1, int len1, const char *val2, int len2) +{ + if (len1 == len2) + return memcmp(val1, val2, len1); + else + return len1 > len2 ? 1 : -1; +} + /* * Compare two jbvString JsonbValue values, a and b. * @@ -1770,21 +1815,12 @@ lengthCompareJsonbStringValue(const void *a, const void *b) { const JsonbValue *va = (const JsonbValue *) a; const JsonbValue *vb = (const JsonbValue *) b; - int res; Assert(va->type == jbvString); Assert(vb->type == jbvString); - if (va->val.string.len == vb->val.string.len) - { - res = memcmp(va->val.string.val, vb->val.string.val, va->val.string.len); - } - else - { - res = (va->val.string.len > vb->val.string.len) ? 1 : -1; - } - - return res; + return lengthCompareJsonbString(va->val.string.val, va->val.string.len, + vb->val.string.val, vb->val.string.len); } /* diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index bcf7b5c8d1..ba0d40b996 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -454,12 +454,6 @@ static Datum populate_array(ArrayIOData *aio, const char *colname, static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, MemoryContext mcxt, JsValue *jsv, bool isnull); -/* Worker that takes care of common setup for us */ -static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, - uint32 flags, - char *key, - uint32 keylen); - /* functions supporting jsonb_delete, jsonb_set and jsonb_concat */ static JsonbValue *IteratorConcat(JsonbIterator **it1, JsonbIterator **it2, JsonbParseState **state); @@ -718,13 +712,15 @@ jsonb_object_field(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); text *key = PG_GETARG_TEXT_PP(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_OBJECT(jb)) PG_RETURN_NULL(); - v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, - VARDATA_ANY(key), - VARSIZE_ANY_EXHDR(key)); + v = jsonbFindKeyInObject(&jb->root, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key), + &vbuf); if (v != NULL) PG_RETURN_JSONB_P(JsonbValueToJsonb(v)); @@ -748,53 +744,61 @@ json_object_field_text(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } +static text * +JsonbValueAsText(JsonbValue *v) +{ + switch (v->type) + { + case jbvNull: + return NULL; + + case jbvBool: + return cstring_to_text(v->val.boolean ? "true" : "false"); + + case jbvString: + return cstring_to_text_with_len(v->val.string.val, v->val.string.len); + + case jbvNumeric: + { + Datum cstr = DirectFunctionCall1(numeric_out, + PointerGetDatum(v->val.numeric)); + + return cstring_to_text(DatumGetCString(cstr)); + } + + case jbvBinary: + { + StringInfoData jtext; + + initStringInfo(&jtext); + (void) JsonbToCString(&jtext, v->val.binary.data, -1); + return cstring_to_text_with_len(jtext.data, jtext.len); + } + + default: + elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); + return NULL; + } +} + Datum jsonb_object_field_text(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); text *key = PG_GETARG_TEXT_PP(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_OBJECT(jb)) PG_RETURN_NULL(); - v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, - VARDATA_ANY(key), - VARSIZE_ANY_EXHDR(key)); - - if (v != NULL) - { - text *result = NULL; - - switch (v->type) - { - case jbvNull: - break; - case jbvBool: - result = cstring_to_text(v->val.boolean ? "true" : "false"); - break; - case jbvString: - result = cstring_to_text_with_len(v->val.string.val, v->val.string.len); - break; - case jbvNumeric: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out, - PointerGetDatum(v->val.numeric)))); - break; - case jbvBinary: - { - StringInfo jtext = makeStringInfo(); - - (void) JsonbToCString(jtext, v->val.binary.data, -1); - result = cstring_to_text_with_len(jtext->data, jtext->len); - } - break; - default: - elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); - } + v = jsonbFindKeyInObject(&jb->root, + VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key), + &vbuf); - if (result) - PG_RETURN_TEXT_P(result); - } + if (v != NULL && v->type != jbvNull) + PG_RETURN_TEXT_P(JsonbValueAsText(v)); PG_RETURN_NULL(); } @@ -820,6 +824,7 @@ jsonb_array_element(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); int element = PG_GETARG_INT32(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_ARRAY(jb)) PG_RETURN_NULL(); @@ -835,7 +840,7 @@ jsonb_array_element(PG_FUNCTION_ARGS) element += nelements; } - v = getIthJsonbValueFromContainer(&jb->root, element); + v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf); if (v != NULL) PG_RETURN_JSONB_P(JsonbValueToJsonb(v)); @@ -863,6 +868,7 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) Jsonb *jb = PG_GETARG_JSONB_P(0); int element = PG_GETARG_INT32(1); JsonbValue *v; + JsonbValue vbuf; if (!JB_ROOT_IS_ARRAY(jb)) PG_RETURN_NULL(); @@ -878,40 +884,10 @@ jsonb_array_element_text(PG_FUNCTION_ARGS) element += nelements; } - v = getIthJsonbValueFromContainer(&jb->root, element); - if (v != NULL) - { - text *result = NULL; - - switch (v->type) - { - case jbvNull: - break; - case jbvBool: - result = cstring_to_text(v->val.boolean ? "true" : "false"); - break; - case jbvString: - result = cstring_to_text_with_len(v->val.string.val, v->val.string.len); - break; - case jbvNumeric: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1(numeric_out, - PointerGetDatum(v->val.numeric)))); - break; - case jbvBinary: - { - StringInfo jtext = makeStringInfo(); - - (void) JsonbToCString(jtext, v->val.binary.data, -1); - result = cstring_to_text_with_len(jtext->data, jtext->len); - } - break; - default: - elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); - } + v = getIthJsonbValueFromContainer(&jb->root, element, &vbuf); - if (result) - PG_RETURN_TEXT_P(result); - } + if (v != NULL && v->type != jbvNull) + PG_RETURN_TEXT_P(JsonbValueAsText(v)); PG_RETURN_NULL(); } @@ -1389,7 +1365,6 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) { Jsonb *jb = PG_GETARG_JSONB_P(0); ArrayType *path = PG_GETARG_ARRAYTYPE_P(1); - Jsonb *res; Datum *pathtext; bool *pathnulls; int npath; @@ -1397,7 +1372,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) bool have_object = false, have_array = false; JsonbValue *jbvp = NULL; - JsonbValue tv; + JsonbValue jbvbuf; JsonbContainer *container; /* @@ -1425,7 +1400,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) Assert(JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb)); /* Extract the scalar value, if it is what we'll return */ if (npath <= 0) - jbvp = getIthJsonbValueFromContainer(container, 0); + jbvp = getIthJsonbValueFromContainer(container, 0, &jbvbuf); } /* @@ -1455,10 +1430,10 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) { if (have_object) { - jbvp = findJsonbValueFromContainerLen(container, - JB_FOBJECT, - VARDATA(pathtext[i]), - VARSIZE(pathtext[i]) - VARHDRSZ); + jbvp = jsonbFindKeyInObject(container, + VARDATA(pathtext[i]), + VARSIZE(pathtext[i]) - VARHDRSZ, + &jbvbuf); } else if (have_array) { @@ -1494,7 +1469,7 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) index = nelements + lindex; } - jbvp = getIthJsonbValueFromContainer(container, index); + jbvp = getIthJsonbValueFromContainer(container, index, &jbvbuf); } else { @@ -1509,41 +1484,32 @@ get_jsonb_path_all(FunctionCallInfo fcinfo, bool as_text) if (jbvp->type == jbvBinary) { - JsonbIterator *it = JsonbIteratorInit((JsonbContainer *) jbvp->val.binary.data); - JsonbIteratorToken r; - - r = JsonbIteratorNext(&it, &tv, true); - container = (JsonbContainer *) jbvp->val.binary.data; - have_object = r == WJB_BEGIN_OBJECT; - have_array = r == WJB_BEGIN_ARRAY; + container = jbvp->val.binary.data; + have_object = JsonContainerIsObject(container); + have_array = JsonContainerIsArray(container); + Assert(!JsonContainerIsScalar(container)); } else { - have_object = jbvp->type == jbvObject; - have_array = jbvp->type == jbvArray; + Assert(IsAJsonbScalar(jbvp)); + have_object = false; + have_array = false; } } if (as_text) { - /* special-case outputs for string and null values */ - if (jbvp->type == jbvString) - PG_RETURN_TEXT_P(cstring_to_text_with_len(jbvp->val.string.val, - jbvp->val.string.len)); - if (jbvp->type == jbvNull) - PG_RETURN_NULL(); - } - - res = JsonbValueToJsonb(jbvp); + text *res = JsonbValueAsText(jbvp); - if (as_text) - { - PG_RETURN_TEXT_P(cstring_to_text(JsonbToCString(NULL, - &res->root, - VARSIZE(res)))); + if (res) + PG_RETURN_TEXT_P(res); + else + PG_RETURN_NULL(); } else { + Jsonb *res = JsonbValueToJsonb(jbvp); + /* not text mode - just hand back the jsonb */ PG_RETURN_JSONB_P(res); } @@ -1760,22 +1726,7 @@ each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text) } else { - text *sv; - - if (v.type == jbvString) - { - /* In text mode, scalar strings should be dequoted */ - sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len); - } - else - { - /* Turn anything else into a json string */ - StringInfo jtext = makeStringInfo(); - Jsonb *jb = JsonbValueToJsonb(&v); - - (void) JsonbToCString(jtext, &jb->root, 0); - sv = cstring_to_text_with_len(jtext->data, jtext->len); - } + text *sv = JsonbValueAsText(&v); values[1] = PointerGetDatum(sv); } @@ -2070,22 +2021,7 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, } else { - text *sv; - - if (v.type == jbvString) - { - /* in text mode scalar strings should be dequoted */ - sv = cstring_to_text_with_len(v.val.string.val, v.val.string.len); - } - else - { - /* turn anything else into a json string */ - StringInfo jtext = makeStringInfo(); - Jsonb *jb = JsonbValueToJsonb(&v); - - (void) JsonbToCString(jtext, &jb->root, 0); - sv = cstring_to_text_with_len(jtext->data, jtext->len); - } + text *sv = JsonbValueAsText(&v); values[0] = PointerGetDatum(sv); } @@ -3130,8 +3066,7 @@ JsObjectGetField(JsObject *obj, char *field, JsValue *jsv) else { jsv->val.jsonb = !obj->val.jsonb_cont ? NULL : - findJsonbValueFromContainerLen(obj->val.jsonb_cont, JB_FOBJECT, - field, strlen(field)); + jsonbFindKeyInObject(obj->val.jsonb_cont, field, strlen(field), NULL); return jsv->val.jsonb != NULL; } @@ -3943,22 +3878,6 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) } } -/* - * findJsonbValueFromContainer() wrapper that sets up JsonbValue key string. - */ -static JsonbValue * -findJsonbValueFromContainerLen(JsonbContainer *container, uint32 flags, - char *key, uint32 keylen) -{ - JsonbValue k; - - k.type = jbvString; - k.val.string.val = key; - k.val.string.len = keylen; - - return findJsonbValueFromContainer(container, flags, &k); -} - /* * Semantic actions for json_strip_nulls. * diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index b294c44f95..fe6b3130ea 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -424,9 +424,9 @@ static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb); static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, - bool isJsonb, JsonItem *val); + bool isJsonb, JsonItem *res); static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, - JsonItem *elem); + JsonItem *res); static void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb); @@ -2922,8 +2922,6 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, JsonItem *value, JsonbValue *baseObject) { Jsonx *vars = varsJsonx; - JsonbValue *val; - JsonbValue key; if (!varName) { @@ -2942,17 +2940,12 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, if (!vars) return -1; - key.type = jbvString; - key.val.string.val = varName; - key.val.string.len = varNameLength; - if (isJsonb) { Jsonb *jb = &vars->jb; - val = findJsonbValueFromContainer(&jb->root, JB_FOBJECT, &key); - - if (!val) + if (!jsonbFindKeyInObject(&jb->root, varName, varNameLength, + JsonItemJbv(value))) return -1; JsonbInitBinary(baseObject, jb); @@ -2961,17 +2954,13 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, { Json *js = &vars->js; - val = findJsonValueFromContainer(&js->root, JB_FOBJECT, &key); - - if (!val) + if (!jsonFindLastKeyInObject(&js->root, varName, varNameLength, + JsonItemJbv(value))) return -1; JsonInitBinary(baseObject, js); } - *JsonItemJbv(value) = *val; - pfree(val); - return 1; } @@ -3555,18 +3544,12 @@ getJsonObjectKey(JsonItem *jsi, char *keystr, int keylen, bool isJsonb, JsonItem *res) { JsonbContainer *jbc = JsonItemBinary(jsi).data; - JsonbValue *val; - JsonbValue key; - - key.type = jbvString; - key.val.string.val = keystr; - key.val.string.len = keylen; - - val = isJsonb ? - findJsonbValueFromContainer(jbc, JB_FOBJECT, &key) : - findJsonValueFromContainer((JsonContainer *) jbc, JB_FOBJECT, &key); + JsonbValue *val = isJsonb ? + jsonbFindKeyInObject(jbc, keystr, keylen, JsonItemJbv(res)) : + jsonFindLastKeyInObject((JsonContainer *) jbc, keystr, keylen, + JsonItemJbv(res)); - return val ? JsonbValueToJsonItem(val, res) : NULL; + return val ? res : NULL; } static JsonItem * @@ -3574,10 +3557,11 @@ getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, JsonItem *res) { JsonbContainer *jbc = JsonItemBinary(jb).data; JsonbValue *elem = isJsonb ? - getIthJsonbValueFromContainer(jbc, index) : - getIthJsonValueFromContainer((JsonContainer *) jbc, index); + getIthJsonbValueFromContainer(jbc, index, JsonItemJbv(res)) : + getIthJsonValueFromContainer((JsonContainer *) jbc, index, + JsonItemJbv(res)); - return elem ? JsonbValueToJsonItem(elem, res) : NULL; + return elem ? res : NULL; } static inline void diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 58d0b94b8c..ab33f7bb50 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -234,9 +234,11 @@ extern char *JsonToCString(StringInfo out, JsonContainer *jc, int estimated_len); extern JsonbValue *pushJsonValue(JsonbParseState **pstate, JsonbIteratorToken tok, JsonbValue *jbv); +extern JsonbValue *jsonFindLastKeyInObject(JsonContainer *obj, + const char *keyval, int keylen, JsonbValue *res); extern JsonbValue *findJsonValueFromContainer(JsonContainer *jc, uint32 flags, - JsonbValue *key); + JsonbValue *key, JsonbValue *res); extern JsonbValue *getIthJsonValueFromContainer(JsonContainer *array, - uint32 index); + uint32 index, JsonbValue *res); #endif /* JSONAPI_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index d71e3f46b8..0a767dfcc8 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -366,10 +366,14 @@ extern int lengthCompareJsonbStringValue(const void *a, const void *b); extern bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b); extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b); extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader, - uint32 flags, - JsonbValue *key); + uint32 flags, JsonbValue *key, + JsonbValue *res); +extern JsonbValue *jsonbFindKeyInObject(JsonbContainer *container, + const char *keyVal, int keyLen, + JsonbValue *res); extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader, - uint32 i); + uint32 i, JsonbValue *result); + extern JsonbValue *pushJsonbValue(JsonbParseState **pstate, JsonbIteratorToken seq, JsonbValue *jbVal); extern JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate, From 82d503734ac90f224051814b69046ece1a46ab76 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 28 Feb 2019 22:29:41 +0300 Subject: [PATCH 53/66] Add jsonpath wrapItem() --- src/backend/utils/adt/jsonpath_exec.c | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index fe6b3130ea..c1cb59a475 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -419,6 +419,7 @@ static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonItem *getNumber(JsonItem *scalar); static bool convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num); static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); +static JsonItem *wrapItem(JsonItem *jbv, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb); @@ -3646,6 +3647,45 @@ convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num) return true; } +/* + * Wrap a non-array SQL/JSON item into an array for applying array subscription + * path steps in lax mode. + */ +static JsonItem * +wrapItem(JsonItem *jsi, bool isJsonb) +{ + JsonbParseState *ps = NULL; + JsonItem jsibuf; + JsonbValue jbvbuf; + + switch (JsonbType(jsi)) + { + case jbvArray: + /* Simply return an array item. */ + return jsi; + + case jbvObject: + /* + * Need to wrap object into a binary JsonbValue for its unpacking + * in pushJsonbValue(). + */ + if (!JsonItemIsBinary(jsi)) + jsi = JsonxWrapInBinary(jsi, &jsibuf, isJsonb); + break; + + default: + /* Ordinary scalars can be pushed directly. */ + break; + } + + pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); + (isJsonb ? pushJsonbValue : pushJsonValue)(&ps, WJB_ELEM, + JsonItemToJsonbValue(jsi, &jbvbuf)); + jsi = JsonbValueToJsonItem(pushJsonbValue(&ps, WJB_END_ARRAY, NULL), &jsibuf); + + return JsonxWrapInBinary(jsi, NULL, isJsonb); +} + /* Construct a JSON array from the item list */ static JsonbValue * wrapItemsInArray(const JsonValueList *items, bool isJsonb) From a1d2662c7e9020e35df9e773625f0f6f45c210a4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sun, 12 Feb 2017 22:01:43 +0300 Subject: [PATCH 54/66] Add jsonb <=> bytea casts --- src/backend/utils/adt/jsonb.c | 17 +++ src/backend/utils/adt/jsonb_util.c | 130 +++++++++++++++++++++++ src/include/catalog/pg_cast.dat | 7 ++ src/include/catalog/pg_proc.dat | 3 + src/include/utils/jsonb.h | 1 + src/test/regress/expected/jsonb.out | 21 ++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/jsonb.sql | 6 ++ src/test/regress/sql/opr_sanity.sql | 4 + 9 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index cd68e9bdd7..c94d545c17 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -238,6 +238,23 @@ jsonb_typeof(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(result)); } +/* + * jsonb_from_bytea -- bytea to jsonb conversion with validation + */ +Datum +jsonb_from_bytea(PG_FUNCTION_ARGS) +{ + bytea *ba = PG_GETARG_BYTEA_P(0); + Jsonb *jb = (Jsonb *) ba; + + if (!JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), + errmsg("incorrect jsonb binary data format"))); + + PG_RETURN_JSONB_P(jb); +} + /* * jsonb_from_cstring * diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 4d5d9f09e9..68ee5b8f6d 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -1892,3 +1892,133 @@ uniqueifyJsonbObject(JsonbValue *object) object->val.object.nPairs = res + 1 - object->val.object.pairs; } } + +bool +JsonbValidate(const void *data, uint32 size) +{ + const JsonbContainer *jbc = data; + const JEntry *entries; + const char *base; + uint32 header; + uint32 nentries; + uint32 offset; + uint32 i; + uint32 nkeys = 0; + + check_stack_depth(); + + /* check header size */ + if (size < offsetof(JsonbContainer, children)) + return false; + + size -= offsetof(JsonbContainer, children); + + /* read header */ + header = jbc->header; + entries = &jbc->children[0]; + nentries = header & JB_CMASK; + + switch (header & ~JB_CMASK) + { + case JB_FOBJECT: + nkeys = nentries; + nentries *= 2; + break; + + case JB_FARRAY | JB_FSCALAR: + if (nentries != 1) + /* scalar pseudo-array must have one element */ + return false; + break; + + case JB_FARRAY: + break; + + default: + /* invalid container type */ + return false; + } + + /* check JEntry array size */ + if (size < sizeof(JEntry) * nentries) + return false; + + size -= sizeof(JEntry) * nentries; + + base = (const char *) &entries[nentries]; + + for (i = 0, offset = 0; i < nentries; i++) + { + JEntry entry = entries[i]; + uint32 offlen = JBE_OFFLENFLD(entry); + uint32 length; + uint32 nextOffset; + + if (JBE_HAS_OFF(entry)) + { + nextOffset = offlen; + length = nextOffset - offset; + } + else + { + length = offlen; + nextOffset = offset + length; + } + + if (nextOffset < offset || nextOffset > size) + return false; + + /* check that object key is string */ + if (i < nkeys && !JBE_ISSTRING(entry)) + return false; + + switch (entry & JENTRY_TYPEMASK) + { + case JENTRY_ISSTRING: + break; + + case JENTRY_ISNUMERIC: + { + uint32 padding = INTALIGN(offset) - offset; + + if (length < padding) + return false; + + if (length - padding < sizeof(struct varlena)) + return false; + + /* TODO JsonValidateNumeric(); */ + break; + } + + case JENTRY_ISBOOL_FALSE: + case JENTRY_ISBOOL_TRUE: + case JENTRY_ISNULL: + if (length) + return false; + + break; + + case JENTRY_ISCONTAINER: + { + uint32 padding = INTALIGN(offset) - offset; + + if (length < padding) + return false; + + if (!JsonbValidate(&base[offset + padding], + length - padding)) + return false; + + break; + } + + default: + return false; + } + + offset = nextOffset; + } + + return true; +} diff --git a/src/include/catalog/pg_cast.dat b/src/include/catalog/pg_cast.dat index aabfa7af03..f19ef807ca 100644 --- a/src/include/catalog/pg_cast.dat +++ b/src/include/catalog/pg_cast.dat @@ -496,6 +496,13 @@ { castsource => 'jsonb', casttarget => 'json', castfunc => '0', castcontext => 'a', castmethod => 'i' }, +# jsonb to/from bytea +{ castsource => 'jsonb', casttarget => 'bytea', castfunc => '0', + castcontext => 'e', castmethod => 'b' }, +{ castsource => 'bytea', casttarget => 'jsonb', + castfunc => 'jsonb_from_bytea(bytea)', + castcontext => 'e', castmethod => 'f' }, + # jsonb to numeric and bool types { castsource => 'jsonb', casttarget => 'bool', castfunc => 'bool(jsonb)', castcontext => 'e', castmethod => 'f' }, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index e83fb25587..9e9cf6d4db 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9131,6 +9131,9 @@ { oid => '6062', descr => 'check jsonb value type', proname => 'jsonb_is_valid', prorettype => 'bool', proargtypes => 'jsonb text', prosrc => 'jsonb_is_valid' }, +{ oid => '6116', descr => 'bytea to jsonb', + proname => 'jsonb_from_bytea', prorettype => 'jsonb', proargtypes => 'bytea', + prosrc => 'jsonb_from_bytea' }, { oid => '3478', proname => 'jsonb_object_field', prorettype => 'jsonb', diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 0a767dfcc8..8d56d664c8 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -388,6 +388,7 @@ extern bool JsonbDeepContains(JsonbIterator **val, extern void JsonbHashScalarValue(const JsonbValue *scalarVal, uint32 *hash); extern void JsonbHashScalarValueExtended(const JsonbValue *scalarVal, uint64 *hash, uint64 seed); +extern bool JsonbValidate(const void *data, uint32 size); /* jsonb.c support functions */ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 469079c5d8..13ce699a12 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -4930,3 +4930,24 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; 12345 (1 row) +-- test jsonb to/from bytea conversion +SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea; + bytea +------------------------------------------------------------------------------------------------------------ + \x0200002001000080010000000a000010140000506162000020000000008001000200004008000090000000302000000000800200 +(1 row) + +SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb; + jsonb +-------------------------- + {"a": 1, "b": [2, true]} +(1 row) + +SELECT 'aaaa'::bytea::jsonb; +ERROR: incorrect jsonb binary data format +SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j; + count +------- + 0 +(1 row) + diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 21de75e656..aea60b53ad 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -879,6 +879,9 @@ WHERE c.castfunc = p.oid AND -- texttoxml(), which does an XML syntax check. -- As of 9.1, this finds the cast from pg_node_tree to text, which we -- intentionally do not provide a reverse pathway for. +-- As of 10.0, this finds the cast from jsonb to bytea, because those are +-- binary-compatible while the reverse goes through jsonb_from_bytea(), +-- which does a jsonb structure validation. SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext FROM pg_cast c WHERE c.castmethod = 'b' AND @@ -898,7 +901,8 @@ WHERE c.castmethod = 'b' AND xml | text | 0 | a xml | character varying | 0 | a xml | character | 0 | a -(10 rows) + jsonb | bytea | 0 | e +(11 rows) -- **************** pg_conversion **************** -- Look for illegal values in pg_conversion fields. diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index ba870872e8..048bee66f6 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -1250,3 +1250,9 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; + +-- test jsonb to/from bytea conversion +SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea; +SELECT '{"a": 1, "b": [2, true]}'::jsonb::bytea::jsonb; +SELECT 'aaaa'::bytea::jsonb; +SELECT count(*) FROM testjsonb WHERE j::bytea::jsonb <> j; diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql index 99cfc7baf3..6cf75410d4 100644 --- a/src/test/regress/sql/opr_sanity.sql +++ b/src/test/regress/sql/opr_sanity.sql @@ -485,6 +485,10 @@ WHERE c.castfunc = p.oid AND -- As of 9.1, this finds the cast from pg_node_tree to text, which we -- intentionally do not provide a reverse pathway for. +-- As of 10.0, this finds the cast from jsonb to bytea, because those are +-- binary-compatible while the reverse goes through jsonb_from_bytea(), +-- which does a jsonb structure validation. + SELECT castsource::regtype, casttarget::regtype, castfunc, castcontext FROM pg_cast c WHERE c.castmethod = 'b' AND From f2cb0853505747f06f7fd4e95339227a10c73402 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 22 Aug 2017 16:17:06 +0300 Subject: [PATCH 55/66] Add FORMAT jsonb --- src/backend/parser/gram.y | 17 +- src/backend/parser/parse_expr.c | 26 +- src/backend/utils/adt/jsonb.c | 9 +- src/include/parser/kwlist.h | 1 + src/test/regress/expected/json_sqljson.out | 28 +- src/test/regress/expected/jsonb_sqljson.out | 312 +++++++++++++++----- src/test/regress/expected/sqljson.out | 266 +++++++++++++---- src/test/regress/sql/json_sqljson.sql | 8 +- src/test/regress/sql/jsonb_sqljson.sql | 154 ++++++---- src/test/regress/sql/sqljson.sql | 47 ++- 10 files changed, 654 insertions(+), 214 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6771aeccb1..d6921fbe9c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -750,7 +750,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG - JSON_QUERY JSON_TABLE JSON_VALUE + JSON_QUERY JSON_TABLE JSON_VALUE JSONB KEY KEYS KEEP @@ -13477,9 +13477,6 @@ a_expr: c_expr { $$ = $1; } JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; $$ = makeJsonIsPredicate($1, format, $4, $5); } - /* - * Required by standard, but it would conflict with expressions - * like: 'str' || format(...) | a_expr FORMAT json_representation IS JSON @@ -13489,7 +13486,6 @@ a_expr: c_expr { $$ = $1; } $3.location = @2; $$ = makeJsonIsPredicate($1, $3, $6, $7); } - */ | a_expr IS NOT JSON json_predicate_type_constraint_opt @@ -13498,9 +13494,6 @@ a_expr: c_expr { $$ = $1; } JsonFormat format = { JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1 }; $$ = makeNotExpr(makeJsonIsPredicate($1, format, $5, $6), @1); } - /* - * Required by standard, but it would conflict with expressions - * like: 'str' || format(...) | a_expr FORMAT json_representation IS NOT JSON @@ -13510,7 +13503,6 @@ a_expr: c_expr { $$ = $1; } $3.location = @2; $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $7, $8), @1); } - */ | DEFAULT { /* @@ -14935,6 +14927,12 @@ json_representation: $$.encoding = $2; $$.location = @1; } + | JSONB + { + $$.type = JS_FORMAT_JSONB; + $$.encoding = JS_ENC_DEFAULT; + $$.location = @1; + } /* | implementation_defined_JSON_representation_option (BSON, AVRO etc) */ ; @@ -16062,6 +16060,7 @@ unreserved_keyword: | INVOKER | ISOLATION | JSON + | JSONB | KEEP | KEY | KEYS diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f87458bd1f..aa61ceff3c 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -3972,6 +3972,15 @@ coerceJsonFuncExpr(ParseState *pstate, Node *expr, JsonReturning *returning, return (Node *) fexpr; } + if (returning->format.type == JS_FORMAT_JSONB && + returning->typid == BYTEAOID && + exprtype == JSONOID) + { + /* cast json to jsonb before encoding into bytea */ + expr = coerce_to_specific_type(pstate, expr, JSONBOID, "JSON_FUNCTION"); + exprtype = JSONBOID; + } + /* try to coerce expression to the output type */ res = coerce_to_target_type(pstate, expr, exprtype, returning->typid, returning->typmod, @@ -4386,9 +4395,12 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) /* prepare input document */ if (exprtype == BYTEAOID) { - expr = makeJsonByteaToTextConversion(expr, &pred->format, - exprLocation(expr)); - exprtype = TEXTOID; + if (pred->format.type != JS_FORMAT_JSONB) + { + expr = makeJsonByteaToTextConversion(expr, &pred->format, + exprLocation(expr)); + exprtype = TEXTOID; + } } else { @@ -4406,6 +4418,12 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) exprtype = TEXTOID; } + if (pred->format.type == JS_FORMAT_JSONB) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, pred->format.location), + errmsg("cannot use FORMAT JSONB for string input types"))); + if (pred->format.encoding != JS_ENC_DEFAULT) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -4426,7 +4444,7 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) fexpr->location = pred->location; } - else if (exprtype == JSONBOID) + else if (exprtype == JSONBOID || exprtype == BYTEAOID) { /* XXX the following expressions also can be used here: * jsonb_type(jsonb) = 'type' (for object and array checks) diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index c94d545c17..84db858aef 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2138,17 +2138,22 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) /* - * jsonb_is_valid -- check jsonb value type + * jsonb_is_valid -- check bytea jsonb validity and its value type */ Datum jsonb_is_valid(PG_FUNCTION_ARGS) { - Jsonb *jb = PG_GETARG_JSONB_P(0); + bytea *ba = PG_GETARG_BYTEA_P(0); text *type = PG_GETARG_TEXT_P(1); + Jsonb *jb = (Jsonb *) ba; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); + if (get_fn_expr_argtype(fcinfo->flinfo, 0) != JSONBOID && + !JsonbValidate(VARDATA(jb), VARSIZE(jb) - VARHDRSZ)) + PG_RETURN_BOOL(false); + if (!PG_ARGISNULL(1) && strncmp("any", VARDATA(type), VARSIZE_ANY_EXHDR(type))) { diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 860d23b238..5383c3c0fc 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -234,6 +234,7 @@ PG_KEYWORD("json_objectagg", JSON_OBJECTAGG, COL_NAME_KEYWORD) PG_KEYWORD("json_query", JSON_QUERY, COL_NAME_KEYWORD) PG_KEYWORD("json_table", JSON_TABLE, COL_NAME_KEYWORD) PG_KEYWORD("json_value", JSON_VALUE, COL_NAME_KEYWORD) +PG_KEYWORD("jsonb", JSONB, UNRESERVED_KEYWORD) PG_KEYWORD("keep", KEEP, UNRESERVED_KEYWORD) PG_KEYWORD("key", KEY, UNRESERVED_KEYWORD) PG_KEYWORD("keys", KEYS, UNRESERVED_KEYWORD) diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index ef854a673f..3939b4ce13 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -687,6 +687,12 @@ SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSON OMIT QUOT \x616161 (1 row) +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSONB 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(json '[1]', '$' WITH WRAPPER OMIT QUOTES); @@ -825,6 +831,12 @@ SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON); [1,2] (1 row) +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSONB); + json_query +------------ + [1,2] +(1 row) + SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea); json_query -------------- @@ -837,6 +849,12 @@ SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON); \x5b312c325d (1 row) +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSONB); + json_query +------------------------------------------------------------ + \x02000040080000900800001020000000008001002000000000800200 +(1 row) + SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); json_query ------------ @@ -849,6 +867,12 @@ SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT \x7b7d (1 row) +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSONB EMPTY OBJECT ON ERROR); + json_query +------------ + \x00000020 +(1 row) + SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); json_query ------------ @@ -1113,7 +1137,7 @@ FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa' ) @@ -1155,7 +1179,7 @@ SELECT * FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS ( diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index 3bfa6e2825..9fd12ca0f1 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -1,17 +1,85 @@ -- JSON_EXISTS +SELECT JSON_EXISTS(NULL FORMAT JSONB, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::text FORMAT JSONB, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::bytea FORMAT JSONB, '$'); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::json FORMAT JSONB, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS(NULL::jsonb FORMAT JSONB, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_exists +------------- + +(1 row) + SELECT JSON_EXISTS(NULL::jsonb, '$'); json_exists ------------- (1 row) +SELECT JSON_EXISTS('' FORMAT JSONB, '$'); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSONB, '$' TRUE ON ERROR); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSONB, '$' FALSE ON ERROR); + json_exists +------------- + f +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSONB, '$' UNKNOWN ON ERROR); + json_exists +------------- + +(1 row) + +SELECT JSON_EXISTS('' FORMAT JSONB, '$' ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: +SELECT JSON_EXISTS(bytea '' FORMAT JSONB, '$' ERROR ON ERROR); +ERROR: incorrect jsonb binary data format SELECT JSON_EXISTS(jsonb '[]', '$'); json_exists ------------- t (1 row) -SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); +SELECT JSON_EXISTS('[]' FORMAT JSONB, '$'); + json_exists +------------- + t +(1 row) + +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSONB) FORMAT JSONB, '$'); json_exists ------------- t @@ -141,12 +209,66 @@ SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); (1 row) -- JSON_VALUE +SELECT JSON_VALUE(NULL FORMAT JSONB, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::text FORMAT JSONB, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::bytea FORMAT JSONB, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::json FORMAT JSONB, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(NULL::jsonb FORMAT JSONB, '$'); +WARNING: FORMAT JSON has no effect for json and jsonb types + json_value +------------ + +(1 row) + SELECT JSON_VALUE(NULL::jsonb, '$'); json_value ------------ (1 row) +SELECT JSON_VALUE('' FORMAT JSONB, '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE('' FORMAT JSONB, '$' NULL ON ERROR); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE('' FORMAT JSONB, '$' DEFAULT '"default value"' ON ERROR); + json_value +----------------- + "default value" +(1 row) + +SELECT JSON_VALUE('' FORMAT JSONB, '$' ERROR ON ERROR); +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: SELECT JSON_VALUE(jsonb 'null', '$'); json_value ------------ @@ -465,14 +587,14 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 + -- 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) + JSON_QUERY(js FORMAT JSONB, '$'), + JSON_QUERY(js FORMAT JSONB, '$' WITHOUT WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH ARRAY WRAPPER) FROM (VALUES - (jsonb 'null'), + ('null'), ('12.3'), ('true'), ('"aaa"'), @@ -490,14 +612,14 @@ FROM (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" + JSON_QUERY(js FORMAT JSONB, 'strict $[*]') AS "unspec", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" FROM (VALUES - (jsonb '1'), + ('1'), ('[]'), ('[null]'), ('[12.3]'), @@ -520,45 +642,51 @@ FROM | | [1, "2", null, [3]] | [1, "2", null, [3]] | [1, "2", null, [3]] (9 rows) -SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text); json_query ------------ "aaa" (1 row) -SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES); json_query ------------ "aaa" (1 row) -SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES ON SCALAR STRING); json_query ------------ "aaa" (1 row) -SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES); json_query ------------ aaa (1 row) -SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES ON SCALAR STRING); json_query ------------ aaa (1 row) -SELECT JSON_QUERY(jsonb '"aaa"', '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' 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); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' 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); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); + json_query +------------ + \x616161 +(1 row) + +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSONB OMIT QUOTES ERROR ON ERROR); json_query ------------ \x616161 @@ -595,63 +723,63 @@ SELECT JSON_QUERY(jsonb '[1]', '$' WITHOUT WRAPPER KEEP QUOTES); [1] (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]'); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]'); json_query ------------ (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' NULL ON EMPTY); json_query ------------ (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY ARRAY ON EMPTY); json_query ------------ [] (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY OBJECT ON EMPTY); json_query ------------ {} (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY); json_query ------------ (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY NULL ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY NULL ON ERROR); json_query ------------ (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); json_query ------------ [] (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); json_query ------------ {} (1 row) -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY ERROR ON ERROR); ERROR: no SQL/JSON item -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON ERROR); json_query ------------ (1 row) -SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_QUERY('[1,2]' FORMAT JSONB, '$[*]' 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]', '$' RETURNING json); @@ -702,6 +830,12 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSON); [1, 2] (1 row) +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING text FORMAT JSONB); + json_query +------------ + [1, 2] +(1 row) + SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea); json_query ---------------- @@ -714,6 +848,12 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSON); \x5b312c20325d (1 row) +SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING bytea FORMAT JSONB); + json_query +------------------------------------------------------------ + \x02000040080000900800001020000000008001002000000000800200 +(1 row) + SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); json_query ------------ @@ -726,6 +866,12 @@ SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT \x7b7d (1 row) +SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING bytea FORMAT JSONB EMPTY OBJECT ON ERROR); + json_query +------------ + \x00000020 +(1 row) + SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); json_query ------------ @@ -853,13 +999,13 @@ CREATE TABLE test_jsonb_constraints ( 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)) + CHECK (JSON_EXISTS(js FORMAT 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]') + CHECK (JSON_QUERY(js FORMAT 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') + CHECK (JSON_QUERY(js FORMAT JSONB, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') ); \d test_jsonb_constraints Table "public.test_jsonb_constraints" @@ -870,21 +1016,21 @@ CREATE TABLE test_jsonb_constraints ( x | jsonb | | | JSON_QUERY('[1, 2]'::jsonb, '$[*]' RETURNING jsonb WITH UNCONDITIONAL WRAPPER NULL ON EMPTY NULL ON ERROR) 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 FALSE ON ERROR)) + "test_jsonb_constraint2" CHECK (JSON_EXISTS(js FORMAT JSONB, '$."a"' PASSING i + 5 AS int, i::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) "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 NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb) - "test_jsonb_constraint5" CHECK (JSON_QUERY(js::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar) + "test_jsonb_constraint4" CHECK (JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb) + "test_jsonb_constraint5" CHECK (JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar) SELECT check_clause FROM information_schema.check_constraints WHERE constraint_name LIKE 'test_jsonb_constraint%'; - check_clause ------------------------------------------------------------------------------------------------------------------------------------ + 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 FALSE ON ERROR)) + (JSON_EXISTS(js FORMAT JSONB, '$."a"' PASSING (i + 5) AS int, (i)::text AS txt, ARRAY[1, 2, 3] AS arr FALSE ON ERROR)) ((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 NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) - ((JSON_QUERY((js)::jsonb, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)) + ((JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING jsonb WITH CONDITIONAL WRAPPER NULL ON EMPTY EMPTY OBJECT ON ERROR) < '[10]'::jsonb)) + ((JSON_QUERY(js FORMAT JSONB, '$."a"' RETURNING character(5) OMIT QUOTES EMPTY ARRAY ON EMPTY NULL ON ERROR) > 'a'::bpchar)) (5 rows) SELECT pg_get_expr(adbin, adrelid) FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; @@ -927,12 +1073,30 @@ ERROR: syntax error at or near ")" LINE 1: SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); ^ -- NULL => empty table -SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar; +SELECT * FROM JSON_TABLE(NULL FORMAT JSONB, '$' COLUMNS (foo int)) bar; foo ----- (0 rows) +-- invalid json => empty table +SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int)) bar; + foo +----- +(0 rows) + +-- invalid json => error +SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int) ERROR ON ERROR) bar; +ERROR: invalid input syntax for type json +DETAIL: The input string ended unexpectedly. +CONTEXT: JSON data, line 1: -- +SELECT * FROM JSON_TABLE('123' FORMAT JSONB, '$' + COLUMNS (item int PATH '$', foo int)) bar; + item | foo +------+----- + 123 | +(1 row) + SELECT * FROM JSON_TABLE(jsonb '123', '$' COLUMNS (item int PATH '$', foo int)) bar; item | foo @@ -947,12 +1111,13 @@ FROM ('1'), ('[]'), ('{}'), - ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]') + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'), + ('err') ) vals(js) LEFT OUTER JOIN -- JSON_TABLE is implicitly lateral JSON_TABLE( - vals.js::jsonb, 'lax $[*]' + vals.js FORMAT JSONB, 'lax $[*]' COLUMNS ( id FOR ORDINALITY, id2 FOR ORDINALITY, -- allowed additional ordinality columns @@ -966,7 +1131,7 @@ FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa' ) @@ -987,13 +1152,14 @@ FROM [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 8 | 8 | | | | | | | | {"aaa": 123} | {"aa | {"aa | {"aaa": 123} | 123 | 123 [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 9 | 9 | | [1,2] | [1,2 | | | "[1,2]" | "[1,2]" | "[1,2]" | "[1, | "[1, | "[1,2]" | | [1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""] | 10 | 10 | | "str" | "str | | | "\"str\"" | "\"str\"" | "\"str\"" | "\"s | "\"s | "\"str\"" | | -(13 rows) + err | | | | | | | | | | | | | | | +(14 rows) -- JSON_TABLE: Test backward parsing CREATE VIEW jsonb_table_view AS SELECT * FROM JSON_TABLE( - jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + 'null' FORMAT JSONB, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" COLUMNS ( id FOR ORDINALITY, id2 FOR ORDINALITY, -- allowed additional ordinality columns @@ -1007,7 +1173,7 @@ SELECT * FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS ( @@ -1050,7 +1216,7 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS "json_table".a21, "json_table".a22 FROM JSON_TABLE( - 'null'::jsonb, '$[*]' AS json_table_path_1 + 'null'::text FORMAT JSONB, '$[*]' AS json_table_path_1 PASSING 1 + 2 AS a, '"foo"'::json AS "b c" @@ -1094,18 +1260,18 @@ CREATE OR REPLACE VIEW public.jsonb_table_view AS PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22)))) ) EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Table Function Scan on "json_table" Output: "json_table".id, "json_table".id2, "json_table"."int", "json_table".text, "json_table"."char(4)", "json_table".bool, "json_table"."numeric", "json_table".js, "json_table".jb, "json_table".jst, "json_table".jsc, "json_table".jsv, "json_table".jsb, "json_table".aaa, "json_table".aaa1, "json_table".a1, "json_table".b1, "json_table".a11, "json_table".a21, "json_table".a22 - Table Function Call: JSON_TABLE('null'::jsonb, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))) + Table Function Call: JSON_TABLE('null'::text FORMAT JSONB, '$[*]' AS json_table_path_1 PASSING 3 AS a, '"foo"'::jsonb AS "b c" COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, "int" integer PATH '$', text text PATH '$', "char(4)" character(4) PATH '$', bool boolean PATH '$', "numeric" numeric PATH '$', js json PATH '$', jb jsonb PATH '$', jst text FORMAT JSON PATH '$', jsc character(4) FORMAT JSON PATH '$', jsv character varying(4) FORMAT JSON PATH '$', jsb jsonb FORMAT JSON PATH '$', aaa integer PATH '$."aaa"', aaa1 integer PATH '$."aaa"', NESTED PATH '$[1]' AS p1 COLUMNS (a1 integer PATH '$."a1"', b1 text PATH '$."b1"', NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text PATH '$."a11"')), NESTED PATH '$[2]' AS p2 COLUMNS ( NESTED PATH '$[*]' AS "p2:1" COLUMNS (a21 text PATH '$."a21"'), NESTED PATH '$[*]' AS p22 COLUMNS (a22 text PATH '$."a22"'))) PLAN (json_table_path_1 OUTER ((p1 OUTER "p1 1") UNION (p2 OUTER ("p2:1" UNION p22))))) (3 rows) -- JSON_TABLE: ON EMPTY/ON ERROR behavior SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js), - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt; + (VALUES ('1'), ('err'), ('"err"')) vals(js), + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$')) jt; js | a -------+--- 1 | 1 @@ -1114,16 +1280,18 @@ FROM SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js) + (VALUES ('1'), ('err'), ('"err"')) vals(js) LEFT OUTER JOIN - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt ON true; -ERROR: invalid input syntax for type integer: "err" +ERROR: invalid input syntax for type json +DETAIL: Token "err" is invalid. +CONTEXT: JSON data, line 1: err SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js) + (VALUES ('1'), ('err'), ('"err"')) vals(js) LEFT OUTER JOIN - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt ON true; ERROR: invalid input syntax for type integer: "err" SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; @@ -1421,7 +1589,7 @@ LINE 2: jsonb 'null', 'strict $[*]' ^ DETAIL: JSON_TABLE columns shall contain explicit AS pathname specification if explicit PLAN clause is used -- JSON_TABLE: plan execution -CREATE TEMP TABLE jsonb_table_test (js jsonb); +CREATE TEMP TABLE jsonb_table_test (js text); INSERT INTO jsonb_table_test VALUES ( '[ @@ -1437,7 +1605,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1466,7 +1634,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1496,7 +1664,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1526,7 +1694,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1556,7 +1724,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1585,7 +1753,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1614,7 +1782,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1642,7 +1810,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1670,7 +1838,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -1701,7 +1869,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index be2add55a5..bf571371fe 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -17,6 +17,12 @@ SELECT JSON_OBJECT(RETURNING json FORMAT JSON); {} (1 row) +SELECT JSON_OBJECT(RETURNING json FORMAT JSONB); + json_object +------------- + {} +(1 row) + SELECT JSON_OBJECT(RETURNING jsonb); json_object ------------- @@ -29,6 +35,12 @@ SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); {} (1 row) +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSONB); + json_object +------------- + {} +(1 row) + SELECT JSON_OBJECT(RETURNING text); json_object ------------- @@ -47,6 +59,16 @@ 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 text FORMAT JSONB); + json_object +------------- + {} +(1 row) + +SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8); +ERROR: syntax error at or near "ENCODING" +LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8... + ^ SELECT JSON_OBJECT(RETURNING bytea); json_object ------------- @@ -75,6 +97,12 @@ 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(RETURNING bytea FORMAT JSONB); + json_object +------------- + \x00000020 +(1 row) + 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); @@ -220,6 +248,12 @@ SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSON); {"a" : {"b" : 1}} (1 row) +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING text) FORMAT JSONB); + json_object +------------------ + {"a" : {"b": 1}} +(1 row) + SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea)); json_object --------------------------------- @@ -232,6 +266,20 @@ SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea) FORMAT JSON); {"a" : {"b" : 1}} (1 row) +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB)); + json_object +--------------------------------------------------------------- + {"a" : "\\x01000020010000800b000010620000002000000000800100"} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB) FORMAT JSONB); + json_object +------------------ + {"a" : {"b": 1}} +(1 row) + +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSON) FORMAT JSONB); +ERROR: incorrect jsonb binary data format SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); json_object ---------------------------------- @@ -301,6 +349,12 @@ SELECT JSON_ARRAY(RETURNING json FORMAT JSON); [] (1 row) +SELECT JSON_ARRAY(RETURNING json FORMAT JSONB); + json_array +------------ + [] +(1 row) + SELECT JSON_ARRAY(RETURNING jsonb); json_array ------------ @@ -313,6 +367,12 @@ SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); [] (1 row) +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSONB); + json_array +------------ + [] +(1 row) + SELECT JSON_ARRAY(RETURNING text); json_array ------------ @@ -331,6 +391,16 @@ 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 text FORMAT JSONB); + json_array +------------ + [] +(1 row) + +SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8); +ERROR: syntax error at or near "ENCODING" +LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8)... + ^ SELECT JSON_ARRAY(RETURNING bytea); json_array ------------ @@ -359,6 +429,12 @@ 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(RETURNING bytea FORMAT JSONB); + json_array +------------ + \x00000040 +(1 row) + SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); json_array --------------------------------------------------- @@ -419,6 +495,24 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT [[{ "a" : 123 }]] (1 row) +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSONB); + json_array +---------------- + [[{"a": 123}]] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB)); + json_array +------------------------------------------------------------------------- + ["\\x01000040180000d001000020010000800b000010610000002000000000807b00"] +(1 row) + +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB) FORMAT JSONB); + json_array +---------------- + [[{"a": 123}]] +(1 row) + SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i)); json_array ------------ @@ -785,8 +879,26 @@ SELECT '' IS JSON; f (1 row) +SELECT '' FORMAT JSON IS JSON; + ?column? +---------- + f +(1 row) + +SELECT '' FORMAT JSONB IS JSON; +ERROR: cannot use FORMAT JSONB for string input types +LINE 1: SELECT '' FORMAT JSONB IS JSON; + ^ SELECT bytea '\x00' IS JSON; ERROR: invalid byte sequence for encoding "UTF8": 0x00 +SELECT bytea '\x00' FORMAT JSON IS JSON; +ERROR: invalid byte sequence for encoding "UTF8": 0x00 +SELECT bytea '\x00' FORMAT JSONB IS JSON; + ?column? +---------- + f +(1 row) + CREATE TABLE test_is_json (js text); INSERT INTO test_is_json VALUES (NULL), @@ -814,27 +926,28 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" 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 + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE | FORMAT JSON IS JSON +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+--------------------- + | | | | | | | | | + | f | t | f | f | f | f | f | f | f + 123 | t | f | t | f | f | t | t | t | t + "aaa " | t | f | t | f | f | t | t | t | t + true | t | f | t | f | f | t | t | t | t + null | t | f | t | f | f | t | t | t | t + [] | t | f | t | f | t | f | t | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t | t + {} | t | f | t | t | f | f | t | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f | t + aaa | f | t | f | f | f | f | f | f | f + {a:1} | f | t | f | f | f | f | f | f | f + ["a",] | f | t | f | f | f | f | f | f | f (16 rows) SELECT @@ -846,22 +959,23 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" 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 + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE | FORMAT JSON IS JSON +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+--------------------- + 123 | t | f | t | f | f | t | t | t | t + "aaa " | t | f | t | f | f | t | t | t | t + true | t | f | t | f | f | t | t | t | t + null | t | f | t | f | f | t | t | t | t + [] | t | f | t | f | t | f | t | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t | t + {} | t | f | t | t | f | f | t | t | t + { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t + { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f | t (11 rows) SELECT @@ -873,22 +987,24 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON", + js FORMAT JSONB IS JSON "FORMAT JSONB IS JSON" 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 + js0 | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE | FORMAT JSON IS JSON | FORMAT JSONB IS JSON +-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+---------------------+---------------------- + 123 | t | f | t | f | f | t | t | t | t | f + "aaa " | t | f | t | f | f | t | t | t | t | f + true | t | f | t | f | f | t | t | t | t | f + null | t | f | t | f | f | t | t | t | t | f + [] | t | f | t | f | t | f | t | t | t | f + [1, "2", {}] | t | f | t | f | t | f | t | t | t | f + {} | t | f | t | t | f | f | t | t | t | f + { "a": 1, "b": null } | t | f | t | t | f | f | t | t | t | f + { "a": 1, "a": null } | t | f | t | t | f | f | t | f | t | f + { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t | t | f + { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f | t | f (11 rows) SELECT @@ -900,22 +1016,50 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" 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 + js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE | FORMAT JSON IS JSON +-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------+--------------------- + 123 | t | f | t | f | f | t | t | t | t + "aaa " | t | f | t | f | f | t | t | t | t + true | t | f | t | f | f | t | t | t | t + null | t | f | t | f | f | t | t | t | t + [] | t | f | t | f | t | f | t | t | t + [1, "2", {}] | t | f | t | f | t | f | t | t | t + {} | t | f | t | t | f | f | t | t | t + {"a": 1, "b": null} | t | f | t | t | f | f | t | t | t + {"a": null} | t | f | t | t | f | f | t | t | t + {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t | t + {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t | t +(11 rows) + +SELECT + js0, + js FORMAT JSONB IS JSON "IS JSON", + js FORMAT JSONB IS NOT JSON "IS NOT JSON", + js FORMAT JSONB IS JSON VALUE "IS VALUE", + js FORMAT JSONB IS JSON OBJECT "IS OBJECT", + js FORMAT JSONB IS JSON ARRAY "IS ARRAY", + js FORMAT JSONB IS JSON SCALAR "IS SCALAR", + js FORMAT JSONB IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js FORMAT JSONB IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::jsonb::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 | t + { "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 | t (11 rows) -- Test IS JSON deparsing diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 8508957c8f..a3b29584a7 100644 --- a/src/test/regress/sql/json_sqljson.sql +++ b/src/test/regress/sql/json_sqljson.sql @@ -191,6 +191,7 @@ SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING text OMIT QUOTES ON SCALAR SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' OMIT QUOTES ERROR ON ERROR); SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING json OMIT QUOTES ERROR ON ERROR); SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSON, '$' RETURNING bytea FORMAT JSONB OMIT QUOTES ERROR ON ERROR); -- QUOTES behavior should not be specified when WITH WRAPPER used: -- Should fail @@ -224,11 +225,14 @@ SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text); SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(10)); SELECT JSON_QUERY(json '[1,2]', '$' RETURNING char(3)); SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSON); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING text FORMAT JSONB); SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea); SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSON); +SELECT JSON_QUERY(json '[1,2]', '$' RETURNING bytea FORMAT JSONB); SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSON EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING bytea FORMAT JSONB EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING json EMPTY OBJECT ON ERROR); SELECT JSON_QUERY(json '[1,2]', '$[*]' RETURNING jsonb EMPTY OBJECT ON ERROR); @@ -354,7 +358,7 @@ FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa' ) @@ -380,7 +384,7 @@ SELECT * FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS ( diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index bae79cb1c7..0979172e16 100644 --- a/src/test/regress/sql/jsonb_sqljson.sql +++ b/src/test/regress/sql/jsonb_sqljson.sql @@ -1,9 +1,23 @@ -- JSON_EXISTS +SELECT JSON_EXISTS(NULL FORMAT JSONB, '$'); +SELECT JSON_EXISTS(NULL::text FORMAT JSONB, '$'); +SELECT JSON_EXISTS(NULL::bytea FORMAT JSONB, '$'); +SELECT JSON_EXISTS(NULL::json FORMAT JSONB, '$'); +SELECT JSON_EXISTS(NULL::jsonb FORMAT JSONB, '$'); SELECT JSON_EXISTS(NULL::jsonb, '$'); +SELECT JSON_EXISTS('' FORMAT JSONB, '$'); +SELECT JSON_EXISTS('' FORMAT JSONB, '$' TRUE ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSONB, '$' FALSE ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSONB, '$' UNKNOWN ON ERROR); +SELECT JSON_EXISTS('' FORMAT JSONB, '$' ERROR ON ERROR); + +SELECT JSON_EXISTS(bytea '' FORMAT JSONB, '$' ERROR ON ERROR); + SELECT JSON_EXISTS(jsonb '[]', '$'); -SELECT JSON_EXISTS(JSON_OBJECT(RETURNING jsonb), '$'); +SELECT JSON_EXISTS('[]' FORMAT JSONB, '$'); +SELECT JSON_EXISTS(JSON_OBJECT(RETURNING bytea FORMAT JSONB) FORMAT JSONB, '$'); SELECT JSON_EXISTS(jsonb '1', '$'); SELECT JSON_EXISTS(jsonb 'null', '$'); @@ -34,8 +48,18 @@ SELECT JSON_EXISTS(jsonb '1', '$.a > 2' ERROR ON ERROR); -- JSON_VALUE +SELECT JSON_VALUE(NULL FORMAT JSONB, '$'); +SELECT JSON_VALUE(NULL::text FORMAT JSONB, '$'); +SELECT JSON_VALUE(NULL::bytea FORMAT JSONB, '$'); +SELECT JSON_VALUE(NULL::json FORMAT JSONB, '$'); +SELECT JSON_VALUE(NULL::jsonb FORMAT JSONB, '$'); SELECT JSON_VALUE(NULL::jsonb, '$'); +SELECT JSON_VALUE('' FORMAT JSONB, '$'); +SELECT JSON_VALUE('' FORMAT JSONB, '$' NULL ON ERROR); +SELECT JSON_VALUE('' FORMAT JSONB, '$' DEFAULT '"default value"' ON ERROR); +SELECT JSON_VALUE('' FORMAT JSONB, '$' ERROR ON ERROR); + SELECT JSON_VALUE(jsonb 'null', '$'); SELECT JSON_VALUE(jsonb 'null', '$' RETURNING int); @@ -124,14 +148,14 @@ SELECT JSON_VALUE(jsonb 'null', '$ts' PASSING timestamptz '2018-02-21 12:34:56 + -- 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) + JSON_QUERY(js FORMAT JSONB, '$'), + JSON_QUERY(js FORMAT JSONB, '$' WITHOUT WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH CONDITIONAL WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH UNCONDITIONAL ARRAY WRAPPER), + JSON_QUERY(js FORMAT JSONB, '$' WITH ARRAY WRAPPER) FROM (VALUES - (jsonb 'null'), + ('null'), ('12.3'), ('true'), ('"aaa"'), @@ -140,14 +164,14 @@ FROM ) 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" + JSON_QUERY(js FORMAT JSONB, 'strict $[*]') AS "unspec", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITHOUT WRAPPER) AS "without", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH CONDITIONAL WRAPPER) AS "with cond", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH UNCONDITIONAL ARRAY WRAPPER) AS "with uncond", + JSON_QUERY(js FORMAT JSONB, 'strict $[*]' WITH ARRAY WRAPPER) AS "with" FROM (VALUES - (jsonb '1'), + ('1'), ('[]'), ('[null]'), ('[12.3]'), @@ -158,14 +182,15 @@ FROM ('[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); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text KEEP QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING text OMIT QUOTES ON SCALAR STRING); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING json OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSON OMIT QUOTES ERROR ON ERROR); +SELECT JSON_QUERY('"aaa"' FORMAT JSONB, '$' RETURNING bytea FORMAT JSONB OMIT QUOTES ERROR ON ERROR); -- QUOTES behavior should not be specified when WITH WRAPPER used: -- Should fail @@ -177,19 +202,19 @@ SELECT JSON_QUERY(jsonb '[1]', '$' WITH CONDITIONAL WRAPPER OMIT QUOTES); 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 ARRAY ON EMPTY); -SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY OBJECT ON EMPTY); -SELECT JSON_QUERY(jsonb '[]', '$[*]' ERROR ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]'); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' NULL ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY ARRAY ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' EMPTY OBJECT ON EMPTY); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR 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('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY NULL ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY ARRAY ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY EMPTY OBJECT ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON EMPTY ERROR ON ERROR); +SELECT JSON_QUERY('[]' FORMAT JSONB, '$[*]' ERROR ON ERROR); -SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' ERROR ON ERROR); +SELECT JSON_QUERY('[1,2]' FORMAT JSONB, '$[*]' ERROR ON ERROR); SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json); SELECT JSON_QUERY(jsonb '[1,2]', '$' RETURNING json FORMAT JSON); @@ -199,11 +224,14 @@ 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 text FORMAT JSONB); 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 FORMAT JSONB); 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 bytea FORMAT JSONB 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); @@ -250,13 +278,13 @@ CREATE TABLE test_jsonb_constraints ( 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)) + CHECK (JSON_EXISTS(js FORMAT 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]') + CHECK (JSON_QUERY(js FORMAT 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') + CHECK (JSON_QUERY(js FORMAT JSONB, '$.a' RETURNING char(5) OMIT QUOTES EMPTY ARRAY ON EMPTY) > 'a') ); \d test_jsonb_constraints @@ -286,9 +314,18 @@ SELECT JSON_TABLE('[]', '$'); SELECT * FROM JSON_TABLE(NULL, '$' COLUMNS ()); -- NULL => empty table -SELECT * FROM JSON_TABLE(NULL::jsonb, '$' COLUMNS (foo int)) bar; +SELECT * FROM JSON_TABLE(NULL FORMAT JSONB, '$' COLUMNS (foo int)) bar; + +-- invalid json => empty table +SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int)) bar; + +-- invalid json => error +SELECT * FROM JSON_TABLE('' FORMAT JSONB, '$' COLUMNS (foo int) ERROR ON ERROR) bar; -- +SELECT * FROM JSON_TABLE('123' FORMAT JSONB, '$' + COLUMNS (item int PATH '$', foo int)) bar; + SELECT * FROM JSON_TABLE(jsonb '123', '$' COLUMNS (item int PATH '$', foo int)) bar; @@ -299,12 +336,13 @@ FROM ('1'), ('[]'), ('{}'), - ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]') + ('[1, 1.23, "2", "aaaaaaa", null, false, true, {"aaa": 123}, "[1,2]", "\"str\""]'), + ('err') ) vals(js) LEFT OUTER JOIN -- JSON_TABLE is implicitly lateral JSON_TABLE( - vals.js::jsonb, 'lax $[*]' + vals.js FORMAT JSONB, 'lax $[*]' COLUMNS ( id FOR ORDINALITY, id2 FOR ORDINALITY, -- allowed additional ordinality columns @@ -318,7 +356,7 @@ FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa' ) @@ -330,7 +368,7 @@ FROM CREATE VIEW jsonb_table_view AS SELECT * FROM JSON_TABLE( - jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" + 'null' FORMAT JSONB, 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" COLUMNS ( id FOR ORDINALITY, id2 FOR ORDINALITY, -- allowed additional ordinality columns @@ -344,7 +382,7 @@ SELECT * FROM jst text FORMAT JSON PATH '$', jsc char(4) FORMAT JSON PATH '$', jsv varchar(4) FORMAT JSON PATH '$', - jsb jsonb FORMAT JSON PATH '$', + jsb jsonb FORMAT JSONB PATH '$', aaa int, -- implicit path '$."aaa"', aaa1 int PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS ( @@ -372,21 +410,21 @@ EXPLAIN (COSTS OFF, VERBOSE) SELECT * FROM jsonb_table_view; -- JSON_TABLE: ON EMPTY/ON ERROR behavior SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js), - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$')) jt; + (VALUES ('1'), ('err'), ('"err"')) vals(js), + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$')) jt; SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js) + (VALUES ('1'), ('err'), ('"err"')) vals(js) LEFT OUTER JOIN - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt ON true; SELECT * FROM - (VALUES ('1'), ('"err"')) vals(js) + (VALUES ('1'), ('err'), ('"err"')) vals(js) LEFT OUTER JOIN - JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt + JSON_TABLE(vals.js FORMAT JSONB, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt ON true; SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt; @@ -624,7 +662,7 @@ SELECT * FROM JSON_TABLE( -- JSON_TABLE: plan execution -CREATE TEMP TABLE jsonb_table_test (js jsonb); +CREATE TEMP TABLE jsonb_table_test (js text); INSERT INTO jsonb_table_test VALUES ( @@ -642,7 +680,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -657,7 +695,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -673,7 +711,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -689,7 +727,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -705,7 +743,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -721,7 +759,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -737,7 +775,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -753,7 +791,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -769,7 +807,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, @@ -785,7 +823,7 @@ select from jsonb_table_test jtt, json_table ( - jtt.js,'strict $[*]' as p + jtt.js FORMAT JSONB,'strict $[*]' as p columns ( n for ordinality, a int path 'lax $.a' default -1 on empty, diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 4f3c06dcb3..b24911adf5 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -2,17 +2,22 @@ SELECT JSON_OBJECT(); SELECT JSON_OBJECT(RETURNING json); SELECT JSON_OBJECT(RETURNING json FORMAT JSON); +SELECT JSON_OBJECT(RETURNING json FORMAT JSONB); SELECT JSON_OBJECT(RETURNING jsonb); SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSON); +SELECT JSON_OBJECT(RETURNING jsonb FORMAT JSONB); 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 text FORMAT JSONB); +SELECT JSON_OBJECT(RETURNING text FORMAT JSONB ENCODING UTF8); 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(RETURNING bytea FORMAT JSONB); SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON); SELECT JSON_OBJECT('foo': NULL::int FORMAT JSON ENCODING UTF8); @@ -67,8 +72,12 @@ SELECT JSON_OBJECT('a': '123', 'b': JSON_OBJECT('a': 111, 'b': 'aaa' RETURNING j 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 text) FORMAT JSONB); 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': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB)); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSONB) FORMAT JSONB); +SELECT JSON_OBJECT('a': JSON_OBJECT('b': 1 RETURNING bytea FORMAT JSON) FORMAT JSONB); SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2); SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL); @@ -91,17 +100,22 @@ SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, 4: NULL, '5': 'a' ABSENT ON NULL WIT SELECT JSON_ARRAY(); SELECT JSON_ARRAY(RETURNING json); SELECT JSON_ARRAY(RETURNING json FORMAT JSON); +SELECT JSON_ARRAY(RETURNING json FORMAT JSONB); SELECT JSON_ARRAY(RETURNING jsonb); SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSON); +SELECT JSON_ARRAY(RETURNING jsonb FORMAT JSONB); 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 text FORMAT JSONB); +SELECT JSON_ARRAY(RETURNING text FORMAT JSONB ENCODING UTF8); 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(RETURNING bytea FORMAT JSONB); SELECT JSON_ARRAY('aaa', 111, true, array[1,2,3], NULL, json '{"a": [1]}', jsonb '["a",3]'); @@ -115,6 +129,9 @@ 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(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSONB); +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB)); +SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING bytea FORMAT JSONB) FORMAT JSONB); 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)); @@ -291,8 +308,12 @@ SELECT NULL::bytea IS JSON; SELECT NULL::int IS JSON; SELECT '' IS JSON; +SELECT '' FORMAT JSON IS JSON; +SELECT '' FORMAT JSONB IS JSON; SELECT bytea '\x00' IS JSON; +SELECT bytea '\x00' FORMAT JSON IS JSON; +SELECT bytea '\x00' FORMAT JSONB IS JSON; CREATE TABLE test_is_json (js text); @@ -323,7 +344,8 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" FROM test_is_json; @@ -336,7 +358,8 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" FROM (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js); @@ -349,7 +372,9 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON", + js FORMAT JSONB IS JSON "FORMAT JSONB IS JSON" FROM (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js); @@ -362,10 +387,24 @@ SELECT 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" + js IS JSON WITH UNIQUE KEYS "WITH UNIQUE", + js FORMAT JSON IS JSON "FORMAT JSON IS JSON" FROM (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js); +SELECT + js0, + js FORMAT JSONB IS JSON "IS JSON", + js FORMAT JSONB IS NOT JSON "IS NOT JSON", + js FORMAT JSONB IS JSON VALUE "IS VALUE", + js FORMAT JSONB IS JSON OBJECT "IS OBJECT", + js FORMAT JSONB IS JSON ARRAY "IS ARRAY", + js FORMAT JSONB IS JSON SCALAR "IS SCALAR", + js FORMAT JSONB IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE", + js FORMAT JSONB IS JSON WITH UNIQUE KEYS "WITH UNIQUE" +FROM + (SELECT js, js::jsonb::bytea FROM test_is_json WHERE js IS JSON) foo(js0, 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; From 32671f2b690998bce520b07d56f8c455efb51517 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 7 Sep 2018 15:01:57 +0300 Subject: [PATCH 56/66] Add outer jsonpath item references --- src/backend/utils/adt/jsonpath.c | 18 ++++++++++ src/backend/utils/adt/jsonpath_exec.c | 34 ++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 18 +++++++++- src/backend/utils/adt/jsonpath_scan.l | 7 ++++ src/include/utils/jsonpath.h | 10 ++++++ src/test/regress/expected/jsonb_jsonpath.out | 35 ++++++++++++++++++++ src/test/regress/expected/jsonpath.out | 28 ++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 8 +++++ src/test/regress/sql/jsonpath.sql | 7 ++++ 9 files changed, 159 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 746087c564..c822239f51 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -358,6 +358,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiAnyArray: case jpiAnyKey: break; + case jpiCurrentN: + if (item->value.current.level < 0 || + item->value.current.level >= nestingLevel) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid outer item reference in jsonpath @"))); + + appendBinaryStringInfo(buf, (char *) &item->value.current.level, + sizeof(item->value.current.level)); + break; case jpiCurrent: if (nestingLevel <= 0) ereport(ERROR, @@ -672,6 +682,10 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, Assert(!inKey); appendStringInfoChar(buf, '@'); break; + case jpiCurrentN: + Assert(!inKey); + appendStringInfo(buf, "@%d", v->content.current.level); + break; case jpiRoot: Assert(!inKey); appendStringInfoChar(buf, '$'); @@ -979,6 +993,9 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiKeyValue: case jpiLast: break; + case jpiCurrentN: + read_int32(v->content.current.level, base, pos); + break; case jpiKey: case jpiString: case jpiVariable: @@ -1075,6 +1092,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiIndexArray || v->type == jpiFilter || v->type == jpiCurrent || + v->type == jpiCurrentN || v->type == jpiExists || v->type == jpiRoot || v->type == jpiVariable || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index c1cb59a475..c031a8fdf2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -120,6 +120,7 @@ typedef struct JsonBaseObjectInfo */ typedef struct JsonItemStackEntry { + JsonBaseObjectInfo base; JsonItem *item; struct JsonItemStackEntry *parent; } JsonItemStackEntry; @@ -443,8 +444,8 @@ static int compareDatetime(Datum val1, Oid typid1, int tz1, Datum val2, Oid typid2, int tz2, bool *error); -static void pushJsonItem(JsonItemStack *stack, - JsonItemStackEntry *entry, JsonItem *item); +static void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, + JsonItem *item, JsonBaseObjectInfo *base); static void popJsonItem(JsonItemStack *stack); static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, @@ -884,7 +885,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, cxt.throwErrors = throwErrors; cxt.isJsonb = isJsonb; - pushJsonItem(&cxt.stack, &root, cxt.root); + pushJsonItem(&cxt.stack, &root, cxt.root, &cxt.baseObject); if (jspStrictAbsenseOfErrors(&cxt) && !result) { @@ -1015,6 +1016,27 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, found, true); break; + case jpiCurrentN: + { + JsonItemStackEntry *current = cxt->stack; + int i; + + for (i = 0; i < jsp->content.current.level; i++) + { + current = current->parent; + + if (!current) + elog(ERROR, "invalid jsonpath current item reference"); + } + + baseObject = cxt->baseObject; + cxt->baseObject = current->base; + jb = current->item; + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + cxt->baseObject = baseObject; + break; + } + case jpiAnyArray: if (JsonbType(jb) == jbvArray) { @@ -2175,7 +2197,7 @@ executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItemStackEntry current; JsonPathBool res; - pushJsonItem(&cxt->stack, ¤t, jb); + pushJsonItem(&cxt->stack, ¤t, jb, &cxt->baseObject); res = executeBoolItem(cxt, jsp, jb, false); popJsonItem(&cxt->stack); @@ -3712,9 +3734,11 @@ wrapItemsInArray(const JsonValueList *items, bool isJsonb) } static void -pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item) +pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item, + JsonBaseObjectInfo *base) { entry->item = item; + entry->base = *base; entry->parent = *stack; *stack = entry; } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 98c2b67518..9f0039855a 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -58,6 +58,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *flags); static JsonPathParseItem *makeItemSequence(List *elems); static JsonPathParseItem *makeItemObject(List *fields); +static JsonPathParseItem *makeItemCurrentN(int level); /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -96,7 +97,7 @@ static JsonPathParseItem *makeItemObject(List *fields); %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P %token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P -%token DATETIME_P +%token DATETIME_P CURRENT_P %type result @@ -211,6 +212,7 @@ path_primary: scalar_value { $$ = $1; } | '$' { $$ = makeItemType(jpiRoot); } | '@' { $$ = makeItemType(jpiCurrent); } + | CURRENT_P { $$ = makeItemCurrentN(pg_atoi(&$1.val[1], 4, 0)); } | LAST_P { $$ = makeItemType(jpiLast); } | '(' expr_seq ')' { $$ = $2; } | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); } @@ -357,6 +359,20 @@ makeItemType(JsonPathItemType type) return v; } +static JsonPathParseItem * +makeItemCurrentN(int level) +{ + JsonPathParseItem *v; + + if (!level) + return makeItemType(jpiCurrent); + + v = makeItemType(jpiCurrentN); + v->value.current.level = level; + + return v; +} + static JsonPathParseItem * makeItemString(JsonPathString *s) { diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 1c291fd914..5b0b441c83 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -194,6 +194,13 @@ hex_fail \\x{hex_dig}{0,1} BEGIN xvq; } +\@[0-9]+ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return CURRENT_P; + } + {special} { return *yytext; } {blank}+ { /* ignore */ } diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 8f29ab7848..8d777d3391 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -73,6 +73,7 @@ typedef enum JsonPathItemType jpiAny, /* .** */ jpiKey, /* .key */ jpiCurrent, /* @ */ + jpiCurrentN, /* @N */ jpiRoot, /* $ */ jpiVariable, /* $variable */ jpiFilter, /* ? (predicate) */ @@ -169,6 +170,11 @@ typedef struct JsonPathItem } *fields; } object; + struct + { + int32 level; + } current; + struct { char *data; /* for bool, numeric and string/key */ @@ -262,6 +268,10 @@ struct JsonPathParseItem List *fields; } object; + struct { + int level; + } current; + /* scalars */ Numeric numeric; bool boolean; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 14cea9901a..b50107b608 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2614,3 +2614,38 @@ select jsonb_path_query('null', '{"a": 1}["b"]'); ------------------ (0 rows) +-- extension: outer item reference (@N) +select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ < @1)))'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ > @1)))'); + jsonb_path_query +------------------ + 5 +(1 row) + +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 2)'); + jsonb_path_query +------------------ + [2, 4, 1, 5, 3] +(1 row) + +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 3)'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[1]) > @2[0]) > @1[0]) > 2)'); + jsonb_path_query +------------------ + [2, 4, 1, 5, 3] +(1 row) + +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) > @2[0]) > @1[0]) > 2)'); + jsonb_path_query +------------------ +(0 rows) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 71daf96bce..6b804d3788 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -1054,3 +1054,31 @@ select '(1.).e3'::jsonpath; ERROR: syntax error, unexpected ')' at or near ")" of jsonpath input LINE 1: select '(1.).e3'::jsonpath; ^ +select '@1'::jsonpath; +ERROR: invalid outer item reference in jsonpath @ +LINE 1: select '@1'::jsonpath; + ^ +select '@-1'::jsonpath; +ERROR: @ is not allowed in root expressions +LINE 1: select '@-1'::jsonpath; + ^ +select '$ ? (@0 > 1)'::jsonpath; + jsonpath +----------- + $?(@ > 1) +(1 row) + +select '$ ? (@1 > 1)'::jsonpath; +ERROR: invalid outer item reference in jsonpath @ +LINE 1: select '$ ? (@1 > 1)'::jsonpath; + ^ +select '$.a ? (@.b ? (@1 > @) > 5)'::jsonpath; + jsonpath +---------------------------- + $."a"?(@."b"?(@1 > @) > 5) +(1 row) + +select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; +ERROR: invalid outer item reference in jsonpath @ +LINE 1: select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; + ^ diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 9f0a66ef92..3522c88161 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -590,3 +590,11 @@ select jsonb_path_query('{"a": 1, "b": 2}', 'lax $["b", "c", "b", "a", 0 to 3]') select jsonb_path_query('null', '{"a": 1}["a"]'); select jsonb_path_query('null', '{"a": 1}["b"]'); + +-- extension: outer item reference (@N) +select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ < @1)))'); +select jsonb_path_query('[2,4,1,5,3]', '$[*] ? (!exists($[*] ? (@ > @1)))'); +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 2)'); +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 3)'); +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[1]) > @2[0]) > @1[0]) > 2)'); +select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) > @2[0]) > @1[0]) > 2)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 0938123348..ade9d111fb 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -198,3 +198,10 @@ select '1..e'::jsonpath; select '1..e3'::jsonpath; select '(1.).e'::jsonpath; select '(1.).e3'::jsonpath; + +select '@1'::jsonpath; +select '@-1'::jsonpath; +select '$ ? (@0 > 1)'::jsonpath; +select '$ ? (@1 > 1)'::jsonpath; +select '$.a ? (@.b ? (@1 > @) > 5)'::jsonpath; +select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; From 5c0f7fbb82e91e76838a6ddf715b552b704bcafa Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 22 May 2017 22:40:58 +0300 Subject: [PATCH 57/66] WIP: jsonpath combination operators --- src/backend/utils/adt/jsonpath.c | 954 +++++++++++++++++++++++-- src/include/catalog/pg_operator.dat | 81 +++ src/include/catalog/pg_proc.dat | 81 +++ src/include/utils/jsonpath.h | 21 +- src/test/regress/expected/jsonpath.out | 227 ++++++ src/test/regress/sql/jsonpath.sql | 49 ++ 6 files changed, 1369 insertions(+), 44 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c822239f51..c927f92153 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -71,18 +71,28 @@ #include "utils/json.h" #include "utils/jsonpath.h" +typedef struct JsonPathContext +{ + StringInfo buf; + Jsonb *vars; +} JsonPathContext; static Datum jsonPathFromCstring(char *in, int len); static char *jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len); -static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, - int nestingLevel, bool insideArraySubscript); +static JsonPath *encodeJsonPath(JsonPathParseItem *item, bool lax, + int32 sizeEstimation, Jsonb *vars); +static int flattenJsonPathParseItem(JsonPathContext *cxt, + JsonPathParseItem *item, int nestingLevel, + bool insideArraySubscript); static void alignStringInfoInt(StringInfo buf); static int32 reserveSpaceForItemPointer(StringInfo buf); static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes); static int operationPriority(JsonPathItemType op); +static bool replaceVariableReference(JsonPathContext *cxt, JsonPathItem *var, + int32 pos); /**************************** INPUT/OUTPUT ********************************/ @@ -169,12 +179,6 @@ jsonPathFromCstring(char *in, int len) { JsonPathParseResult *jsonpath = parsejsonpath(in, len); JsonPath *res; - StringInfoData buf; - - initStringInfo(&buf); - enlargeStringInfo(&buf, 4 * len /* estimation */ ); - - appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); if (!jsonpath) ereport(ERROR, @@ -182,15 +186,39 @@ jsonPathFromCstring(char *in, int len) errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath", in))); - flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false); + res = encodeJsonPath(jsonpath->expr, jsonpath->lax, + 4 * len /* estimation */ , NULL); + + PG_RETURN_JSONPATH_P(res); +} + +static JsonPath * +encodeJsonPath(JsonPathParseItem *item, bool lax, int32 sizeEstimation, + Jsonb *vars) +{ + JsonPath *res; + JsonPathContext cxt; + StringInfoData buf; + + if (!item) + return NULL; + + initStringInfo(&buf); + enlargeStringInfo(&buf, sizeEstimation); + + appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); + + cxt.buf = &buf; + cxt.vars = vars; + flattenJsonPathParseItem(&cxt, item, 0, false); res = (JsonPath *) buf.data; SET_VARSIZE(res, buf.len); res->header = JSONPATH_VERSION; - if (jsonpath->lax) + if (lax) res->header |= JSONPATH_LAX; - PG_RETURN_JSONPATH_P(res); + return res; } /* @@ -221,40 +249,357 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) return out->data; } +/*****************************INPUT/OUTPUT************************************/ + +static inline int32 +appendJsonPathItemHeader(StringInfo buf, JsonPathItemType type) +{ + appendStringInfoChar(buf, (char) type); + + /* + * We align buffer to int32 because a series of int32 values often goes + * after the header, and we want to read them directly by dereferencing + * int32 pointer (see jspInitByBuffer()). + */ + alignStringInfoInt(buf); + + /* + * Reserve space for next item pointer. Actual value will be recorded + * later, after next and children items processing. + */ + return reserveSpaceForItemPointer(buf); +} + +static int32 +copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level, + int32 *pLastOffset, int32 *pNextOffset) +{ + StringInfo buf = cxt->buf; + int32 pos = buf->len - JSONPATH_HDRSZ; + JsonPathItem next; + int32 offs = 0; + int32 argLevel = level; + int32 nextOffs; + + check_stack_depth(); + + nextOffs = appendJsonPathItemHeader(buf, item->type); + + switch (item->type) + { + case jpiNull: + case jpiCurrent: + case jpiAnyArray: + case jpiAnyKey: + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiKeyValue: + case jpiLast: + break; + + case jpiRoot: + if (level > 0) + { + /* replace $ with @N */ + int32 lev = level - 1; + + buf->data[pos + JSONPATH_HDRSZ] = + lev > 0 ? jpiCurrentN : jpiCurrent; + + if (lev > 0) + appendBinaryStringInfo(buf, (const char *) &lev, sizeof(lev)); + } + break; + + case jpiCurrentN: + appendBinaryStringInfo(buf, (char *) &item->content.current.level, + sizeof(item->content.current.level)); + break; + + case jpiKey: + case jpiString: + case jpiVariable: + { + int32 len; + char *data = jspGetString(item, &len); + + if (item->type == jpiVariable && cxt->vars && + replaceVariableReference(cxt, item, pos)) + break; + + appendBinaryStringInfo(buf, (const char *) &len, sizeof(len)); + appendBinaryStringInfo(buf, data, len); + appendStringInfoChar(buf, '\0'); + break; + } + + case jpiNumeric: + { + Numeric num = jspGetNumeric(item); + + appendBinaryStringInfo(buf, (char *) num, VARSIZE(num)); + break; + } + + case jpiBool: + appendStringInfoChar(buf, jspGetBool(item) ? 1 : 0); + break; + + case jpiFilter: + if (level) + argLevel++; + /* fall through */ + case jpiNot: + case jpiExists: + case jpiIsUnknown: + case jpiPlus: + case jpiMinus: + case jpiDatetime: + case jpiArray: + { + JsonPathItem arg; + int32 argoffs; + int32 argpos; + + argoffs = buf->len; + appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs)); + + if (!item->content.arg) + break; + + jspGetArg(item, &arg); + argpos = copyJsonPathItem(cxt, &arg, argLevel, NULL, NULL); + *(int32 *) &buf->data[argoffs] = argpos - pos; + break; + } + + case jpiAnd: + case jpiOr: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiStartsWith: + { + JsonPathItem larg; + JsonPathItem rarg; + int32 loffs; + int32 roffs; + int32 lpos; + int32 rpos; + + loffs = buf->len; + appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs)); + + roffs = buf->len; + appendBinaryStringInfo(buf, (const char *) &offs, sizeof(offs)); + + jspGetLeftArg(item, &larg); + lpos = copyJsonPathItem(cxt, &larg, argLevel, NULL, NULL); + *(int32 *) &buf->data[loffs] = lpos - pos; + + jspGetRightArg(item, &rarg); + rpos = copyJsonPathItem(cxt, &rarg, argLevel, NULL, NULL); + *(int32 *) &buf->data[roffs] = rpos - pos; + + break; + } + + case jpiLikeRegex: + { + JsonPathItem expr; + int32 eoffs; + int32 epos; + + appendBinaryStringInfo(buf, + (char *) &item->content.like_regex.flags, + sizeof(item->content.like_regex.flags)); + + eoffs = buf->len; + appendBinaryStringInfo(buf, (char *) &offs /* fake value */, sizeof(offs)); + + appendBinaryStringInfo(buf, + (char *) &item->content.like_regex.patternlen, + sizeof(item->content.like_regex.patternlen)); + appendBinaryStringInfo(buf, item->content.like_regex.pattern, + item->content.like_regex.patternlen); + appendStringInfoChar(buf, '\0'); + + jspInitByBuffer(&expr, item->base, item->content.like_regex.expr); + epos = copyJsonPathItem(cxt, &expr, argLevel, NULL, NULL); + *(int32 *) &buf->data[eoffs] = epos - pos; + } + break; + + case jpiIndexArray: + { + int32 nelems = item->content.array.nelems; + int32 i; + int offset; + + appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); + offset = buf->len; + appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); + + for (i = 0; i < nelems; i++, offset += 2 * sizeof(int32)) + { + JsonPathItem from; + JsonPathItem to; + int32 *ppos; + int32 frompos; + int32 topos; + bool range; + + range = jspGetArraySubscript(item, &from, &to, i); + + frompos = copyJsonPathItem(cxt, &from, argLevel, NULL, NULL) - pos; + + if (range) + topos = copyJsonPathItem(cxt, &to, argLevel, NULL, NULL) - pos; + else + topos = 0; + + ppos = (int32 *) &buf->data[offset]; + ppos[0] = frompos; + ppos[1] = topos; + } + } + break; + + case jpiAny: + appendBinaryStringInfo(buf, (char *) &item->content.anybounds.first, + sizeof(item->content.anybounds.first)); + appendBinaryStringInfo(buf, (char *) &item->content.anybounds.last, + sizeof(item->content.anybounds.last)); + break; + + case jpiSequence: + { + int32 nelems = item->content.sequence.nelems; + int32 i; + int offset; + + appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); + offset = buf->len; + appendStringInfoSpaces(buf, sizeof(int32) * nelems); + + for (i = 0; i < nelems; i++, offset += sizeof(int32)) + { + JsonPathItem el; + int32 elpos; + + jspGetSequenceElement(item, i, &el); + + elpos = copyJsonPathItem(cxt, &el, level, NULL, NULL); + *(int32 *) &buf->data[offset] = elpos - pos; + } + } + break; + + case jpiObject: + { + int32 nfields = item->content.object.nfields; + int32 i; + int offset; + + appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields)); + offset = buf->len; + appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields); + + for (i = 0; i < nfields; i++, offset += 2 * sizeof(int32)) + { + JsonPathItem key; + JsonPathItem val; + int32 keypos; + int32 valpos; + int32 *ppos; + + jspGetObjectField(item, i, &key, &val); + + keypos = copyJsonPathItem(cxt, &key, level, NULL, NULL); + valpos = copyJsonPathItem(cxt, &val, level, NULL, NULL); + + ppos = (int32 *) &buf->data[offset]; + ppos[0] = keypos - pos; + ppos[1] = valpos - pos; + } + } + break; + + default: + elog(ERROR, "Unknown jsonpath item type: %d", item->type); + } + + if (jspGetNext(item, &next)) + { + int32 nextPos = copyJsonPathItem(cxt, &next, level, + pLastOffset, pNextOffset); + + *(int32 *) &buf->data[nextOffs] = nextPos - pos; + } + else if (pLastOffset) + { + *pLastOffset = pos; + *pNextOffset = nextOffs; + } + + return pos; +} + +static int32 +copyJsonPath(JsonPathContext *cxt, JsonPath *jp, int level, int32 *last, int32 *next) +{ + JsonPathItem root; + + alignStringInfoInt(cxt->buf); + + jspInit(&root, jp); + + return copyJsonPathItem(cxt, &root, level, last, next); +} + /* * Recursive function converting given jsonpath parse item and all its * children into a binary representation. */ static int -flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, +flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item, int nestingLevel, bool insideArraySubscript) { + StringInfo buf = cxt->buf; /* position from beginning of jsonpath data */ int32 pos = buf->len - JSONPATH_HDRSZ; int32 chld; int32 next; - int argNestingLevel = 0; + int32 last; + int argNestingLevel = nestingLevel; check_stack_depth(); CHECK_FOR_INTERRUPTS(); - appendStringInfoChar(buf, (char) (item->type)); - - /* - * We align buffer to int32 because a series of int32 values often goes - * after the header, and we want to read them directly by dereferencing - * int32 pointer (see jspInitByBuffer()). - */ - alignStringInfoInt(buf); - - /* - * Reserve space for next item pointer. Actual value will be recorded - * later, after next and children items processing. - */ - next = reserveSpaceForItemPointer(buf); + if (item->type == jpiBinary) + pos = copyJsonPath(cxt, item->value.binary, nestingLevel, &last, &next); + else + { + next = appendJsonPathItemHeader(buf, item->type); + last = pos; + } switch (item->type) { + case jpiBinary: + break; case jpiString: case jpiVariable: case jpiKey: @@ -297,14 +642,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 right = reserveSpaceForItemPointer(buf); chld = !item->value.args.left ? pos : - flattenJsonPathParseItem(buf, item->value.args.left, - nestingLevel + argNestingLevel, + flattenJsonPathParseItem(cxt, item->value.args.left, + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + left) = chld - pos; chld = !item->value.args.right ? pos : - flattenJsonPathParseItem(buf, item->value.args.right, - nestingLevel + argNestingLevel, + flattenJsonPathParseItem(cxt, item->value.args.right, + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + right) = chld - pos; } @@ -324,7 +669,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, item->value.like_regex.patternlen); appendStringInfoChar(buf, '\0'); - chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, + chld = flattenJsonPathParseItem(cxt, item->value.like_regex.expr, nestingLevel, insideArraySubscript); *(int32 *) (buf->data + offs) = chld - pos; @@ -345,8 +690,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (!item->value.arg) break; - chld = flattenJsonPathParseItem(buf, item->value.arg, - nestingLevel + argNestingLevel, + chld = flattenJsonPathParseItem(cxt, item->value.arg, + argNestingLevel, insideArraySubscript); *(int32 *) (buf->data + arg) = chld - pos; } @@ -397,12 +742,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 *ppos; int32 topos; int32 frompos = - flattenJsonPathParseItem(buf, - item->value.array.elems[i].from, - nestingLevel, true) - pos; + flattenJsonPathParseItem(cxt, + item->value.array.elems[i].from, + nestingLevel, true) - pos; if (item->value.array.elems[i].to) - topos = flattenJsonPathParseItem(buf, + topos = flattenJsonPathParseItem(cxt, item->value.array.elems[i].to, nestingLevel, true) - pos; else @@ -446,7 +791,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, foreach(lc, item->value.sequence.elems) { int32 elempos = - flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel, + flattenJsonPathParseItem(cxt, lfirst(lc), nestingLevel, insideArraySubscript); *(int32 *) &buf->data[offset] = elempos - pos; @@ -470,11 +815,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, { JsonPathParseItem *field = lfirst(lc); int32 keypos = - flattenJsonPathParseItem(buf, field->value.args.left, + flattenJsonPathParseItem(cxt, field->value.args.left, nestingLevel, insideArraySubscript); int32 valpos = - flattenJsonPathParseItem(buf, field->value.args.right, + flattenJsonPathParseItem(cxt, field->value.args.right, nestingLevel, insideArraySubscript); int32 *ppos = (int32 *) &buf->data[offset]; @@ -492,8 +837,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next, nestingLevel, - insideArraySubscript) - pos; + chld = flattenJsonPathParseItem(cxt, item->next, nestingLevel, + insideArraySubscript) - last; *(int32 *) (buf->data + next) = chld; } @@ -1238,3 +1583,526 @@ jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val) jspInitByBuffer(key, v->base, v->content.object.fields[i].key); jspInitByBuffer(val, v->base, v->content.object.fields[i].val); } + +static void +checkJsonPathArgsMismatch(JsonPath *jp1, JsonPath *jp2) +{ + if ((jp1->header & ~JSONPATH_LAX) != JSONPATH_VERSION || + jp1->header != jp2->header) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("jsonpath headers does not match"))); +} + +static inline JsonPathParseItem * +jspInitParseItem(JsonPathParseItem *item, JsonPathItemType type, + JsonPathParseItem *next) +{ + if (!item) + item = palloc(sizeof(*item)); + + item->type = type; + item->next = next; + + return item; +} + +static inline void +jspInitParseItemUnary(JsonPathParseItem *item, JsonPathItemType type, + JsonPathParseItem *next, JsonPathParseItem *arg) +{ + item = jspInitParseItem(item, type, next); + item->value.arg = arg; +} + +static inline void +jspInitParseItemBinary(JsonPathParseItem *item, JsonPathItemType type, + JsonPathParseItem *left, JsonPathParseItem *right, + JsonPathParseItem *next) +{ + item = jspInitParseItem(item, type, next); + item->value.args.left = left; + item->value.args.right = right; +} + +static inline void +jspInitParseItemBin(JsonPathParseItem *item, JsonPath *path, + JsonPathParseItem *next) +{ + item = jspInitParseItem(item, jpiBinary, next); + item->value.binary = path; +} + +static inline void +jspInitParseItemString(JsonPathParseItem *item, JsonPathItemType type, + char *str, uint32 len, JsonPathParseItem *next) +{ + item = jspInitParseItem(item, type, next); + item->value.string.val = str; + item->value.string.len = len; +} + +static JsonPathParseItem * +jspInitParseItemJsonbScalar(JsonPathParseItem *item, JsonbValue *jbv) +{ + /* jbv and jpi scalar types have the same values */ + item = jspInitParseItem(item, (JsonPathItemType) jbv->type, NULL); + + switch (jbv->type) + { + case jbvNull: + break; + + case jbvBool: + item->value.boolean = jbv->val.boolean; + break; + + case jbvString: + item->value.string.val = jbv->val.string.val; + item->value.string.len = jbv->val.string.len; + break; + + case jbvNumeric: + item->value.numeric = jbv->val.numeric; + break; + + default: + elog(ERROR, "invalid scalar jsonb value type: %d", jbv->type); + break; + } + + return item; +} + +static JsonPathParseItem * +jspInitParseItemJsonb(JsonPathParseItem *item, Jsonb *jb) +{ + JsonbValue jbv; + + if (JB_ROOT_IS_SCALAR(jb)) + { + JsonbExtractScalar(&jb->root, &jbv); + + return jspInitParseItemJsonbScalar(item, &jbv); + } + else + { + JsonbIterator *it; + JsonbIteratorToken tok; + JsonPathParseItem *res = NULL; + JsonPathParseItem *stack = NULL; + + it = JsonbIteratorInit(&jb->root); + + while ((tok = JsonbIteratorNext(&it, &jbv, false)) != WJB_DONE) + { + switch (tok) + { + case WJB_BEGIN_OBJECT: + /* push object */ + stack = jspInitParseItem(NULL, jpiObject, stack); + stack->value.object.fields = NIL; + break; + + case WJB_BEGIN_ARRAY: + /* push array */ + stack = jspInitParseItem(NULL, jpiArray, stack); + stack->value.arg = jspInitParseItem(NULL, jpiSequence, NULL); + stack->value.arg->value.sequence.elems = NIL; + break; + + case WJB_END_OBJECT: + case WJB_END_ARRAY: + /* save and pop current container */ + res = stack; + stack = stack->next; + res->next = NULL; + + if (stack) + { + if (stack->type == jpiArray) + { + /* add container to the list of array elements */ + stack->value.arg->value.sequence.elems = + lappend(stack->value.arg->value.sequence.elems, + res); + } + else if (stack->type == jpiObjectField) + { + /* save result into the object field value */ + stack->value.args.right = res; + + /* pop current object field */ + res = stack; + stack = stack->next; + res->next = NULL; + Assert(stack && stack->type == jpiObject); + } + } + break; + + case WJB_KEY: + { + JsonPathParseItem *key = palloc0(sizeof(*key)); + JsonPathParseItem *field = palloc0(sizeof(*field)); + + Assert(stack->type == jpiObject); + + jspInitParseItem(field, jpiObjectField, stack); + field->value.args.left = key; + field->value.args.right = NULL; + + jspInitParseItemJsonbScalar(key, &jbv); + + stack->value.object.fields = + lappend(stack->value.object.fields, field); + + /* push current object field */ + stack = field; + break; + } + + case WJB_VALUE: + Assert(stack->type == jpiObjectField); + stack->value.args.right = + jspInitParseItemJsonbScalar(NULL, &jbv); + + /* pop current object field */ + res = stack; + stack = stack->next; + res->next = NULL; + Assert(stack && stack->type == jpiObject); + break; + + case WJB_ELEM: + Assert(stack->type == jpiArray); + stack->value.arg->value.sequence.elems = + lappend(stack->value.arg->value.sequence.elems, + jspInitParseItemJsonbScalar(NULL, &jbv)); + break; + + default: + elog(ERROR, "unexpected jsonb iterator token: %d", tok); + } + } + + return res; + } +} + +/* Subroutine for implementation of operators jsonpath OP jsonpath */ +static Datum +jsonpath_op_jsonpath(FunctionCallInfo fcinfo, JsonPathItemType op) +{ + JsonPath *jp1 = PG_GETARG_JSONPATH_P(0); + JsonPath *jp2 = PG_GETARG_JSONPATH_P(1); + JsonPathParseItem jpi1; + JsonPathParseItem jpi2; + JsonPathParseItem jpi; + + checkJsonPathArgsMismatch(jp1, jp2); + + jspInitParseItemBin(&jpi1, jp1, NULL); + jspInitParseItemBin(&jpi2, jp2, NULL); + jspInitParseItemBinary(&jpi, op, &jpi1, &jpi2, NULL); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jpi, + (jp1->header & JSONPATH_LAX) != 0, + VARSIZE(jp1) + VARSIZE(jp2) - + JSONPATH_HDRSZ + 16, NULL)); +} + +/* Subroutine for implementation of operators jsonpath OP jsonb */ +static Datum +jsonpath_op_jsonb(FunctionCallInfo fcinfo, JsonPathItemType op) +{ + JsonPath *jp = PG_GETARG_JSONPATH_P(0); + Jsonb *jb = PG_GETARG_JSONB_P(1); + JsonPathParseItem jpi1; + JsonPathParseItem *jpi2; + JsonPathParseItem jpi; + + jspInitParseItemBin(&jpi1, jp, NULL); + jpi2 = jspInitParseItemJsonb(NULL, jb); + jspInitParseItemBinary(&jpi, op, &jpi1, jpi2, NULL); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jpi, + (jp->header & JSONPATH_LAX) != 0, + VARSIZE(jp) + VARSIZE(jb), NULL)); +} + +/* Implementation of operator jsonpath == jsonpath */ +Datum +jsonpath_eq_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiEqual); +} + +/* Implementation of operator jsonpath == jsonb */ +Datum +jsonpath_eq_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiEqual); +} + +/* Implementation of operator jsonpath != jsonpath */ +Datum +jsonpath_ne_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiNotEqual); +} + +/* Implementation of operator jsonpath != jsonb */ +Datum +jsonpath_ne_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiNotEqual); +} + +/* Implementation of operator jsonpath < jsonpath */ +Datum +jsonpath_lt_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiLess); +} + +/* Implementation of operator jsonpath < jsonb */ +Datum +jsonpath_lt_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiLess); +} + +/* Implementation of operator jsonpath <= jsonpath */ +Datum +jsonpath_le_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiLessOrEqual); +} + +/* Implementation of operator jsonpath <= jsonb */ +Datum +jsonpath_le_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiLessOrEqual); +} + +/* Implementation of operator jsonpath > jsonpath */ +Datum +jsonpath_gt_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiGreater); +} + +/* Implementation of operator jsonpath > jsonb */ +Datum +jsonpath_gt_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiGreater); +} + +/* Implementation of operator jsonpath >= jsonpath */ +Datum +jsonpath_ge_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiGreaterOrEqual); +} + +/* Implementation of operator jsonpath >= jsonb */ +Datum +jsonpath_ge_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiGreaterOrEqual); +} + +/* Implementation of operator jsonpath + jsonpath */ +Datum +jsonpath_pl_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiAdd); +} + +/* Implementation of operator jsonpath + jsonb */ +Datum +jsonpath_pl_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiAdd); +} + +/* Implementation of operator jsonpath - jsonpath */ +Datum +jsonpath_mi_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiSub); +} + +/* Implementation of operator jsonpath - jsonb */ +Datum +jsonpath_mi_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiSub); +} + +/* Implementation of operator jsonpath / jsonpath */ +Datum +jsonpath_mul_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiMul); +} + +/* Implementation of operator jsonpath * jsonb */ +Datum +jsonpath_mul_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiMul); +} + +/* Implementation of operator jsonpath / jsonpath */ +Datum +jsonpath_div_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiDiv); +} + +/* Implementation of operator jsonpath / jsonb */ +Datum +jsonpath_div_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiDiv); +} + +/* Implementation of operator jsonpath % jsonpath */ +Datum +jsonpath_mod_jsonpath(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonpath(fcinfo, jpiMod); +} + +/* Implementation of operator jsonpath % jsonb */ +Datum +jsonpath_mod_jsonb(PG_FUNCTION_ARGS) +{ + return jsonpath_op_jsonb(fcinfo, jpiMod); +} + +/* Implementation of operator jsonpath -> text */ +Datum +jsonpath_object_field(PG_FUNCTION_ARGS) +{ + JsonPath *jpObj = PG_GETARG_JSONPATH_P(0); + text *jpFld = PG_GETARG_TEXT_PP(1); + JsonPathParseItem jpiObj; + JsonPathParseItem jpiFld; + + jspInitParseItemBin(&jpiObj, jpObj, &jpiFld); + jspInitParseItemString(&jpiFld, jpiKey, + VARDATA_ANY(jpFld), VARSIZE_ANY_EXHDR(jpFld), NULL); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jpiObj, + (jpObj->header & JSONPATH_LAX) != 0, + INTALIGN(VARSIZE(jpObj)) + 8 + + jpiFld.value.string.len, NULL)); +} + +/* Implementation of operator jsonpath -> int */ +Datum +jsonpath_array_element(PG_FUNCTION_ARGS) +{ + JsonPath *arr = PG_GETARG_JSONPATH_P(0); + int32 idx = PG_GETARG_INT32(1); + JsonPathParseItem jpiArr; + JsonPathParseItem jpiArrIdx; + JsonPathParseItem jpiIdx; + struct JsonPathParseArraySubscript subscript; + + jspInitParseItemBin(&jpiArr, arr, &jpiArrIdx); + + jspInitParseItem(&jpiArrIdx, jpiIndexArray, NULL); + jpiArrIdx.value.array.nelems = 1; + jpiArrIdx.value.array.elems = &subscript; + + subscript.from = &jpiIdx; + subscript.to = NULL; + + jspInitParseItem(&jpiIdx, jpiNumeric, NULL); + jpiIdx.value.numeric = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(idx))); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jpiArr, + (arr->header & JSONPATH_LAX) != 0, + INTALIGN(VARSIZE(arr)) + 28 + + VARSIZE(jpiIdx.value.numeric), NULL)); +} + +/* Implementation of operator jsonpath ? jsonpath */ +Datum +jsonpath_filter(PG_FUNCTION_ARGS) +{ + JsonPath *jpRoot = PG_GETARG_JSONPATH_P(0); + JsonPath *jpFilter = PG_GETARG_JSONPATH_P(1); + JsonPathItem root; + JsonPathParseItem jppiRoot; + JsonPathParseItem jppiFilter; + JsonPathParseItem jppiFilterArg; + + checkJsonPathArgsMismatch(jpRoot, jpFilter); + + jspInit(&root, jpFilter); + + if (!jspIsBooleanOp(root.type)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("jsonpath filter must be boolean expression"))); + + jspInitParseItemBin(&jppiRoot, jpRoot, &jppiFilter); + jspInitParseItemUnary(&jppiFilter, jpiFilter, NULL, &jppiFilterArg); + jspInitParseItemBin(&jppiFilterArg, jpFilter, NULL); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jppiRoot, + (jpRoot->header & JSONPATH_LAX) != 0, + INTALIGN(VARSIZE(jpRoot)) + 12 + + VARSIZE(jpFilter), NULL)); +} + +static bool +replaceVariableReference(JsonPathContext *cxt, JsonPathItem *var, int32 pos) +{ + JsonbValue name; + JsonbValue valuebuf; + JsonbValue *value; + JsonPathParseItem tmp; + JsonPathParseItem *item; + + name.type = jbvString; + name.val.string.val = jspGetString(var, &name.val.string.len); + + value = findJsonbValueFromContainer(&cxt->vars->root, JB_FOBJECT, &name, + &valuebuf); + + if (!value) + return false; + + cxt->buf->len = pos + JSONPATH_HDRSZ; /* reset buffer */ + + item = jspInitParseItemJsonb(&tmp, JsonbValueToJsonb(value)); + + flattenJsonPathParseItem(cxt, item, false, false); + + return true; +} + +/* Implementation of operator jsonpath @ jsonb */ +Datum +jsonpath_bind_jsonb(PG_FUNCTION_ARGS) +{ + JsonPath *jpRoot = PG_GETARG_JSONPATH_P(0); + Jsonb *jbVars = PG_GETARG_JSONB_P(1); + JsonPathParseItem jppiRoot; + + jspInitParseItemBin(&jppiRoot, jpRoot, NULL); + + PG_RETURN_JSONPATH_P(encodeJsonPath(&jppiRoot, + (jpRoot->header & JSONPATH_LAX) != 0, + INTALIGN(VARSIZE(jpRoot)) + + VARSIZE(jbVars) * 2, jbVars)); +} diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index ccb1eebed2..83e12f1d00 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3272,4 +3272,85 @@ oprresult => 'bool', oprcode => 'json_path_match(json,jsonpath)', oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6017', descr => 'jsonpath == jsonpath', + oprname => '==', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_eq_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6018', descr => 'jsonpath != jsonpath', + oprname => '!=', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_ne_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6019', descr => 'jsonpath < jsonpath', + oprname => '<', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_lt_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6020', descr => 'jsonpath <= jsonpath', + oprname => '<=', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_le_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6021', descr => 'jsonpath > jsonpath', + oprname => '>', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_gt_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6022', descr => 'jsonpath >= jsonpath', + oprname => '>=', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_ge_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6023', descr => 'jsonpath + jsonpath', + oprname => '+', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_pl_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6024', descr => 'jsonpath - jsonpath', + oprname => '-', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_mi_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6025', descr => 'jsonpath * jsonpath', + oprname => '*', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_mul_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6026', descr => 'jsonpath / jsonpath', + oprname => '/', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_div_jsonpath(jsonpath,jsonpath)' }, +{ oid => '6027', descr => 'jsonpath % jsonpath', + oprname => '%', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_mod_jsonpath(jsonpath,jsonpath)' }, + +{ oid => '6029', descr => 'jsonpath == jsonb', + oprname => '==', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_eq_jsonb(jsonpath,jsonb)' }, +{ oid => '6030', descr => 'jsonpath != jsonb', + oprname => '!=', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_ne_jsonb(jsonpath,jsonb)' }, +{ oid => '6031', descr => 'jsonpath < jsonb', + oprname => '<', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_lt_jsonb(jsonpath,jsonb)' }, +{ oid => '6032', descr => 'jsonpath <= jsonb', + oprname => '<=', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_le_jsonb(jsonpath,jsonb)' }, +{ oid => '6033', descr => 'jsonpath > jsonb', + oprname => '>', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_gt_jsonb(jsonpath,jsonb)' }, +{ oid => '6034', descr => 'jsonpath >= jsonb', + oprname => '>=', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_ge_jsonb(jsonpath,jsonb)' }, +{ oid => '6035', descr => 'jsonpath + jsonb', + oprname => '+', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_pl_jsonb(jsonpath,jsonb)' }, +{ oid => '6036', descr => 'jsonpath - jsonb', + oprname => '-', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_mi_jsonb(jsonpath,jsonb)' }, +{ oid => '6037', descr => 'jsonpath * jsonb', + oprname => '*', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_mul_jsonb(jsonpath,jsonb)' }, +{ oid => '6038', descr => 'jsonpath / jsonb', + oprname => '/', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_div_jsonb(jsonpath,jsonb)' }, +{ oid => '6039', descr => 'jsonpath % jsonb', + oprname => '%', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_mod_jsonb(jsonpath,jsonb)' }, + +{ oid => '6040', descr => 'jsonpath -> text', + oprname => '->', oprleft => 'jsonpath', oprright => 'text', + oprresult => 'jsonpath', oprcode => 'jsonpath_object_field(jsonpath,text)' }, +{ oid => '6041', descr => 'jsonpath -> int', + oprname => '->', oprleft => 'jsonpath', oprright => 'int4', + oprresult => 'jsonpath', oprcode => 'jsonpath_array_element(jsonpath,int4)' }, +{ oid => '6042', descr => 'jsonpath ? jsonpath', + oprname => '?', oprleft => 'jsonpath', oprright => 'jsonpath', + oprresult => 'jsonpath', oprcode => 'jsonpath_filter(jsonpath,jsonpath)' }, +{ oid => '6072', descr => 'jsonpath @ jsonb', + oprname => '@', oprleft => 'jsonpath', oprright => 'jsonb', + oprresult => 'jsonpath', oprcode => 'jsonpath_bind_jsonb(jsonpath,jsonb)' }, + ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 9e9cf6d4db..29537a3860 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9385,6 +9385,87 @@ proargtypes => 'json jsonpath json bool', prosrc => 'json_path_query_first_text' }, +{ oid => '6077', descr => 'implementation of == operator', + proname => 'jsonpath_eq_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_eq_jsonpath' }, +{ oid => '6078', descr => 'implementation of != operator', + proname => 'jsonpath_ne_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_ne_jsonpath' }, +{ oid => '6079', descr => 'implementation of < operator', + proname => 'jsonpath_lt_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_lt_jsonpath' }, +{ oid => '6080', descr => 'implementation of <= operator', + proname => 'jsonpath_le_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_le_jsonpath' }, +{ oid => '6081', descr => 'implementation of > operator', + proname => 'jsonpath_gt_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_gt_jsonpath' }, +{ oid => '6082', descr => 'implementation of >= operator', + proname => 'jsonpath_ge_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_ge_jsonpath' }, +{ oid => '6083', descr => 'implementation of + operator', + proname => 'jsonpath_pl_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_pl_jsonpath' }, +{ oid => '6084', descr => 'implementation of - operator', + proname => 'jsonpath_mi_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mi_jsonpath' }, +{ oid => '6085', descr => 'implementation of * operator', + proname => 'jsonpath_mul_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mul_jsonpath' }, +{ oid => '6086', descr => 'implementation of / operator', + proname => 'jsonpath_div_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_div_jsonpath' }, +{ oid => '6087', descr => 'implementation of % operator', + proname => 'jsonpath_mod_jsonpath', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_mod_jsonpath' }, + +{ oid => '6088', descr => 'implementation of == operator', + proname => 'jsonpath_eq_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_eq_jsonb' }, +{ oid => '6089', descr => 'implementation of != operator', + proname => 'jsonpath_ne_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_ne_jsonb' }, +{ oid => '6090', descr => 'implementation of < operator', + proname => 'jsonpath_lt_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_lt_jsonb' }, +{ oid => '6091', descr => 'implementation of <= operator', + proname => 'jsonpath_le_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_le_jsonb' }, +{ oid => '6092', descr => 'implementation of > operator', + proname => 'jsonpath_gt_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_gt_jsonb' }, +{ oid => '6093', descr => 'implementation of >= operator', + proname => 'jsonpath_ge_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_ge_jsonb' }, +{ oid => '6094', descr => 'implementation of + operator', + proname => 'jsonpath_pl_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_pl_jsonb' }, +{ oid => '6095', descr => 'implementation of - operator', + proname => 'jsonpath_mi_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mi_jsonb' }, +{ oid => '6096', descr => 'implementation of * operator', + proname => 'jsonpath_mul_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mul_jsonb' }, +{ oid => '6097', descr => 'implementation of / operator', + proname => 'jsonpath_div_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_div_jsonb' }, +{ oid => '6098', descr => 'implementation of % operator', + proname => 'jsonpath_mod_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_mod_jsonb' }, + +{ oid => '6099', descr => 'implementation of -> operator', + proname => 'jsonpath_object_field', prorettype => 'jsonpath', + proargtypes => 'jsonpath text', prosrc => 'jsonpath_object_field' }, +{ oid => '6015', descr => 'implementation of -> operator', + proname => 'jsonpath_array_element', prorettype => 'jsonpath', + proargtypes => 'jsonpath int4', prosrc => 'jsonpath_array_element' }, +{ oid => '6016', descr => 'implementation of ? operator', + proname => 'jsonpath_filter', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonpath', prosrc => 'jsonpath_filter' }, +{ oid => '6028', descr => 'implementation of @ operator', + proname => 'jsonpath_bind_jsonb', prorettype => 'jsonpath', + proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_bind_jsonb' }, + # txid { oid => '2939', descr => 'I/O', proname => 'txid_snapshot_in', prorettype => 'txid_snapshot', diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 8d777d3391..0fb93de7ea 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -94,6 +94,8 @@ typedef enum JsonPathItemType jpiArray, /* array constructor: '[expr, ...]' */ jpiObject, /* object constructor: '{ key : value, ... }' */ jpiObjectField, /* element of object constructor: 'key : value' */ + + jpiBinary = 0xFF /* for jsonpath operators implementation only */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ @@ -103,6 +105,21 @@ typedef enum JsonPathItemType #define JSP_REGEX_WSPACE 0x08 /* x flag, expanded syntax */ #define JSP_REGEX_QUOTE 0x10 /* q flag, no special characters */ +#define jspIsBooleanOp(type) ( \ + (type) == jpiAnd || \ + (type) == jpiOr || \ + (type) == jpiNot || \ + (type) == jpiIsUnknown || \ + (type) == jpiEqual || \ + (type) == jpiNotEqual || \ + (type) == jpiLess || \ + (type) == jpiGreater || \ + (type) == jpiLessOrEqual || \ + (type) == jpiGreaterOrEqual || \ + (type) == jpiExists || \ + (type) == jpiStartsWith \ +) + /* * Support functions to parse/construct binary value. * Unlike many other representation of expression the first/main @@ -238,7 +255,7 @@ struct JsonPathParseItem struct { int nelems; - struct + struct JsonPathParseArraySubscript { JsonPathParseItem *from; JsonPathParseItem *to; @@ -272,6 +289,8 @@ struct JsonPathParseItem int level; } current; + JsonPath *binary; + /* scalars */ Numeric numeric; bool boolean; diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 6b804d3788..387e7feeb0 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -1082,3 +1082,230 @@ select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; ERROR: invalid outer item reference in jsonpath @ LINE 1: select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; ^ +-- jsonpath combination operators +select jsonpath '$.a' == jsonpath '$[*] + 1'; + ?column? +--------------------- + ($."a" == $[*] + 1) +(1 row) + +-- should fail +select jsonpath '$.a' == jsonpath '$.b == 1'; + ?column? +------------------------- + ($."a" == ($."b" == 1)) +(1 row) + +--select jsonpath '$.a' != jsonpath '$[*] + 1'; +select jsonpath '$.a' > jsonpath '$[*] + 1'; + ?column? +-------------------- + ($."a" > $[*] + 1) +(1 row) + +select jsonpath '$.a' < jsonpath '$[*] + 1'; + ?column? +-------------------- + ($."a" < $[*] + 1) +(1 row) + +select jsonpath '$.a' >= jsonpath '$[*] + 1'; + ?column? +--------------------- + ($."a" >= $[*] + 1) +(1 row) + +select jsonpath '$.a' <= jsonpath '$[*] + 1'; + ?column? +--------------------- + ($."a" <= $[*] + 1) +(1 row) + +select jsonpath '$.a' + jsonpath '$[*] + 1'; + ?column? +---------------------- + ($."a" + ($[*] + 1)) +(1 row) + +select jsonpath '$.a' - jsonpath '$[*] + 1'; + ?column? +---------------------- + ($."a" - ($[*] + 1)) +(1 row) + +select jsonpath '$.a' * jsonpath '$[*] + 1'; + ?column? +---------------------- + ($."a" * ($[*] + 1)) +(1 row) + +select jsonpath '$.a' / jsonpath '$[*] + 1'; + ?column? +---------------------- + ($."a" / ($[*] + 1)) +(1 row) + +select jsonpath '$.a' % jsonpath '$[*] + 1'; + ?column? +---------------------- + ($."a" % ($[*] + 1)) +(1 row) + +select jsonpath '$.a' == jsonb '"aaa"'; + ?column? +------------------ + ($."a" == "aaa") +(1 row) + +--select jsonpath '$.a' != jsonb '1'; +select jsonpath '$.a' > jsonb '12.34'; + ?column? +----------------- + ($."a" > 12.34) +(1 row) + +select jsonpath '$.a' < jsonb '"aaa"'; + ?column? +----------------- + ($."a" < "aaa") +(1 row) + +select jsonpath '$.a' >= jsonb 'true'; + ?column? +----------------- + ($."a" >= true) +(1 row) + +select jsonpath '$.a' <= jsonb 'false'; + ?column? +------------------ + ($."a" <= false) +(1 row) + +select jsonpath '$.a' + jsonb 'null'; + ?column? +---------------- + ($."a" + null) +(1 row) + +select jsonpath '$.a' - jsonb '12.3'; + ?column? +---------------- + ($."a" - 12.3) +(1 row) + +select jsonpath '$.a' * jsonb '5'; + ?column? +------------- + ($."a" * 5) +(1 row) + +select jsonpath '$.a' / jsonb '0'; + ?column? +------------- + ($."a" / 0) +(1 row) + +select jsonpath '$.a' % jsonb '"1.23"'; + ?column? +------------------ + ($."a" % "1.23") +(1 row) + +select jsonpath '$.a' == jsonb '[]'; + ?column? +--------------- + ($."a" == []) +(1 row) + +select jsonpath '$.a' >= jsonb '[1, "2", true, null, [], {"a": [1], "b": 3}]'; + ?column? +--------------------------------------------------------- + ($."a" >= [1, "2", true, null, [], {"a": [1], "b": 3}]) +(1 row) + +select jsonpath '$.a' + jsonb '{}'; + ?column? +-------------- + ($."a" + {}) +(1 row) + +select jsonpath '$.a' / jsonb '{"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}}'; + ?column? +-------------------------------------------------------------------------------- + ($."a" / {"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}}) +(1 row) + +select jsonpath '$' -> 'a'; + ?column? +---------- + $."a" +(1 row) + +select jsonpath '$' -> 1; + ?column? +---------- + $[1] +(1 row) + +select jsonpath '$' -> 'a' -> 1; + ?column? +---------- + $."a"[1] +(1 row) + +select jsonpath '$.a' ? jsonpath '$.x ? (@.y ? (@ > 3 + @1.b + $) == $) > $.z'; + ?column? +------------------------------------------------------------- + $."a"?(@."x"?(@."y"?(@ > (3 + @1."b") + @2) == @1) > @."z") +(1 row) + +select jsonpath '$.a.b[($[*]?(@ > @0).c + 1.23).**{2 to 5}] ? ({a: @, b: [$.x, [], @ % 5]}.b[2] > 3)' ? + jsonpath '$.**[$.size() + 3] ? (@ + $ ? (@ > @1 ? ($1 + $2 * @ - $ != 5) / $) < 10) > true'; + ?column? +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + $."a"."b"[$[*]?(@ > @)."c" + 1.23.**{2 to 5}]?({"a": @, "b": [$."x", [], @ % 5]}."b"[2] > 3)?(@.**[@.size() + 3]?(@ + @1?(@ > @1?(($"1" + $"2" * @) - @3 != 5) / @2) < 10) > true) +(1 row) + +select jsonpath '$.a + $a' @ jsonb '"aaa"'; + ?column? +---------------- + ($."a" + $"a") +(1 row) + +select jsonpath '$.a + $a' @ jsonb '{"b": "abc"}'; + ?column? +---------------- + ($."a" + $"a") +(1 row) + +select jsonpath '$.a + $a' @ jsonb '{"a": "abc"}'; + ?column? +----------------- + ($."a" + "abc") +(1 row) + +select jsonpath '$.a + $a.double()' @ jsonb '{"a": "abc"}'; + ?column? +-------------------------- + ($."a" + "abc".double()) +(1 row) + +select jsonpath '$.a + $a.x.double()' @ jsonb '{"a": {"x": -12.34}}'; + ?column? +-------------------------------------- + ($."a" + {"x": -12.34}."x".double()) +(1 row) + +select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23, "max": 5.0}'; + ?column? +------------------------------ + $[*]?(@ > -1.23 && @ <= 5.0) +(1 row) + +select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ jsonb '{"max": 5.0}'; + ?column? +------------------------------ + $[*]?(@ > -1.23 && @ <= 5.0) +(1 row) + diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index ade9d111fb..bafc1375fc 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -205,3 +205,52 @@ select '$ ? (@0 > 1)'::jsonpath; select '$ ? (@1 > 1)'::jsonpath; select '$.a ? (@.b ? (@1 > @) > 5)'::jsonpath; select '$.a ? (@.b ? (@2 > @) > 5)'::jsonpath; + +-- jsonpath combination operators + +select jsonpath '$.a' == jsonpath '$[*] + 1'; +-- should fail +select jsonpath '$.a' == jsonpath '$.b == 1'; +--select jsonpath '$.a' != jsonpath '$[*] + 1'; +select jsonpath '$.a' > jsonpath '$[*] + 1'; +select jsonpath '$.a' < jsonpath '$[*] + 1'; +select jsonpath '$.a' >= jsonpath '$[*] + 1'; +select jsonpath '$.a' <= jsonpath '$[*] + 1'; +select jsonpath '$.a' + jsonpath '$[*] + 1'; +select jsonpath '$.a' - jsonpath '$[*] + 1'; +select jsonpath '$.a' * jsonpath '$[*] + 1'; +select jsonpath '$.a' / jsonpath '$[*] + 1'; +select jsonpath '$.a' % jsonpath '$[*] + 1'; + +select jsonpath '$.a' == jsonb '"aaa"'; +--select jsonpath '$.a' != jsonb '1'; +select jsonpath '$.a' > jsonb '12.34'; +select jsonpath '$.a' < jsonb '"aaa"'; +select jsonpath '$.a' >= jsonb 'true'; +select jsonpath '$.a' <= jsonb 'false'; +select jsonpath '$.a' + jsonb 'null'; +select jsonpath '$.a' - jsonb '12.3'; +select jsonpath '$.a' * jsonb '5'; +select jsonpath '$.a' / jsonb '0'; +select jsonpath '$.a' % jsonb '"1.23"'; +select jsonpath '$.a' == jsonb '[]'; +select jsonpath '$.a' >= jsonb '[1, "2", true, null, [], {"a": [1], "b": 3}]'; +select jsonpath '$.a' + jsonb '{}'; +select jsonpath '$.a' / jsonb '{"a": 1, "b": [1, {}], "c": {}, "d": {"e": true, "f": {"g": "abc"}}}'; + + +select jsonpath '$' -> 'a'; +select jsonpath '$' -> 1; +select jsonpath '$' -> 'a' -> 1; +select jsonpath '$.a' ? jsonpath '$.x ? (@.y ? (@ > 3 + @1.b + $) == $) > $.z'; + +select jsonpath '$.a.b[($[*]?(@ > @0).c + 1.23).**{2 to 5}] ? ({a: @, b: [$.x, [], @ % 5]}.b[2] > 3)' ? + jsonpath '$.**[$.size() + 3] ? (@ + $ ? (@ > @1 ? ($1 + $2 * @ - $ != 5) / $) < 10) > true'; + +select jsonpath '$.a + $a' @ jsonb '"aaa"'; +select jsonpath '$.a + $a' @ jsonb '{"b": "abc"}'; +select jsonpath '$.a + $a' @ jsonb '{"a": "abc"}'; +select jsonpath '$.a + $a.double()' @ jsonb '{"a": "abc"}'; +select jsonpath '$.a + $a.x.double()' @ jsonb '{"a": {"x": -12.34}}'; +select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23, "max": 5.0}'; +select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ jsonb '{"max": 5.0}'; From ff88671e2c3da58436c73f7cadc7a75b44aa35c4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 9 Dec 2017 00:50:13 +0300 Subject: [PATCH 58/66] Add jsonpath item flags --- src/backend/utils/adt/jsonpath.c | 9 ++++++--- src/backend/utils/adt/jsonpath_gram.y | 1 + src/include/utils/jsonpath.h | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c927f92153..a1dc6591ce 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -252,9 +252,10 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len) /*****************************INPUT/OUTPUT************************************/ static inline int32 -appendJsonPathItemHeader(StringInfo buf, JsonPathItemType type) +appendJsonPathItemHeader(StringInfo buf, JsonPathItemType type, char flags) { appendStringInfoChar(buf, (char) type); + appendStringInfoChar(buf, (char) flags); /* * We align buffer to int32 because a series of int32 values often goes @@ -283,7 +284,7 @@ copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level, check_stack_depth(); - nextOffs = appendJsonPathItemHeader(buf, item->type); + nextOffs = appendJsonPathItemHeader(buf, item->type, item->flags); switch (item->type) { @@ -592,7 +593,7 @@ flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item, pos = copyJsonPath(cxt, item->value.binary, nestingLevel, &last, &next); else { - next = appendJsonPathItemHeader(buf, item->type); + next = appendJsonPathItemHeader(buf, item->type, item->flags); last = pos; } @@ -1319,6 +1320,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) v->base = base + pos; read_byte(v->type, base, pos); + read_byte(v->flags, base, pos); pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base; read_int32(v->nextPos, base, pos); @@ -1602,6 +1604,7 @@ jspInitParseItem(JsonPathParseItem *item, JsonPathItemType type, item = palloc(sizeof(*item)); item->type = type; + item->flags = 0; item->next = next; return item; diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 9f0039855a..81df3eda1e 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -354,6 +354,7 @@ makeItemType(JsonPathItemType type) CHECK_FOR_INTERRUPTS(); v->type = type; + v->flags = 0; v->next = NULL; return v; diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 0fb93de7ea..a415864b03 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -131,6 +131,7 @@ typedef enum JsonPathItemType typedef struct JsonPathItem { JsonPathItemType type; + uint8 flags; /* position form base to next node */ int32 nextPos; @@ -236,6 +237,7 @@ typedef struct JsonPathParseItem JsonPathParseItem; struct JsonPathParseItem { JsonPathItemType type; + uint8 flags; JsonPathParseItem *next; /* next in path */ union From 889305d68c7e2907966b55c839c67b2d46357710 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 4 Apr 2017 13:06:25 +0300 Subject: [PATCH 59/66] WIP: Add jsonpath branch returning --- src/backend/utils/adt/jsonpath_exec.c | 162 ++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 43 ++++- src/include/utils/jsonpath.h | 4 + src/test/regress/expected/jsonb_jsonpath.out | 46 ++++++ src/test/regress/sql/jsonb_jsonpath.sql | 9 ++ 5 files changed, 245 insertions(+), 19 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index c031a8fdf2..a19019da51 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -336,7 +336,7 @@ static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb); static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found, - uint32 level, uint32 first, uint32 last, + bool outPath, uint32 level, uint32 first, uint32 last, bool ignoreStructuralErrors, bool unwrapNext); static JsonPathBool executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred, JsonPathItem *larg, @@ -405,6 +405,7 @@ static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt, JsonItem *jsi, int32 id); static void JsonValueListClear(JsonValueList *jvl); static void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv); +static void JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2); static int JsonValueListLength(const JsonValueList *jvl); static bool JsonValueListIsEmpty(JsonValueList *jvl); static JsonItem *JsonValueListHead(JsonValueList *jvl); @@ -424,6 +425,10 @@ static JsonItem *wrapItem(JsonItem *jbv, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb); +static void appendWrappedItems(JsonValueList *found, JsonValueList *items, + bool isJsonb); +static JsonValueList prependKey(char *keystr, int keylen, + const JsonValueList *items, bool isJsonb); static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, bool isJsonb, JsonItem *res); @@ -978,7 +983,18 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jb != NULL) { - res = executeNextItem(cxt, jsp, NULL, jb, found, true); + JsonValueList items = {0}; + JsonValueList *pitems = found; + + if (pitems && jspOutPath(jsp)) + pitems = &items; + + res = executeNextItem(cxt, jsp, NULL, jb, pitems, true); + + if (!JsonValueListIsEmpty(&items) && !jperIsError(res)) + JsonValueListConcat(found, prependKey(key, keylen, + &items, + cxt->isJsonb)); } else if (!jspIgnoreStructuralErrors(cxt)) { @@ -1040,10 +1056,19 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAnyArray: if (JsonbType(jb) == jbvArray) { + JsonValueList items = {0}; + JsonValueList *pitems = found; + bool wrap = pitems && jspOutPath(jsp); bool hasNext = jspGetNext(jsp, &elem); + if (wrap) + pitems = &items; + res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL, - jb, found, jspAutoUnwrap(cxt)); + jb, pitems, jspAutoUnwrap(cxt)); + + if (wrap && !jperIsError(res)) + appendWrappedItems(found, &items, cxt->isJsonb); } else if (jspAutoWrap(cxt)) res = executeNextItem(cxt, jsp, NULL, jb, found, true); @@ -1059,6 +1084,12 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, int innermostArraySize = cxt->innermostArraySize; int i; JsonItem bin; + JsonValueList items = {0}; + JsonValueList *pitems = found; + bool wrap = pitems && jspOutPath(jsp); + + if (wrap) + pitems = &items; jb = wrapJsonObjectOrArray(jb, &bin, cxt->isJsonb); @@ -1094,7 +1125,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (index_from <= 0 && index_to >= 0) { - res = executeNextItem(cxt, jsp, NULL, jb, found, + res = executeNextItem(cxt, jsp, NULL, jb, pitems, true); if (jperIsError(res)) return res; @@ -1137,7 +1168,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!index) { - res = executeNextItem(cxt, jsp, NULL, jb, found, + res = executeNextItem(cxt, jsp, NULL, jb, pitems, true); if (jperIsError(res)) return res; @@ -1158,7 +1189,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (val) { - res = executeNextItem(cxt, jsp, NULL, val, found, + res = executeNextItem(cxt, jsp, NULL, val, pitems, true); if (jperIsError(res)) return res; @@ -1178,6 +1209,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } cxt->innermostArraySize = innermostArraySize; + + if (wrap && !jperIsError(res)) + appendWrappedItems(found, &items, cxt->isJsonb); } else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt)) { @@ -1186,8 +1220,14 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, int size = JsonxArraySize(jb, cxt->isJsonb); bool binary = JsonItemIsBinary(jb); bool singleton = size < 0; + JsonValueList items = {0}; + JsonValueList *pitems = found; + bool wrap = pitems && jspOutPath(jsp); bool hasNext = jspGetNext(jsp, &elem); + if (wrap) + pitems = &items; + if (singleton) size = 1; @@ -1260,7 +1300,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!hasNext && !found) return jperOk; - res = executeNextItem(cxt, jsp, &elem, jsi, found, + res = executeNextItem(cxt, jsp, &elem, jsi, pitems, true); if (jperIsError(res)) @@ -1278,6 +1318,9 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } cxt->innermostArraySize = innermostArraySize; + + if (wrap && !jperIsError(res)) + appendWrappedItems(found, &items, cxt->isJsonb); } else if (!jspIgnoreStructuralErrors(cxt)) { @@ -1325,7 +1368,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeAnyItem (cxt, hasNext ? &elem : NULL, - JsonItemBinary(jb).data, found, 1, 1, 1, + JsonItemBinary(jb).data, found, jspOutPath(jsp), 1, 1, 1, false, jspAutoUnwrap(cxt)); } else if (unwrap && JsonbType(jb) == jbvArray) @@ -1403,8 +1446,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors; cxt->ignoreStructuralErrors = true; - res = executeNextItem(cxt, jsp, &elem, - jb, found, true); + res = executeNextItem(cxt, jsp, &elem, jb, found, true); cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors; if (res == jperOk && !found) @@ -1414,7 +1456,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, if (JsonItemIsBinary(jb)) res = executeAnyItem (cxt, hasNext ? &elem : NULL, - JsonItemBinary(jb).data, found, + JsonItemBinary(jb).data, found, jspOutPath(jsp), 1, jsp->content.anybounds.first, jsp->content.anybounds.last, @@ -1950,7 +1992,7 @@ executeItemUnwrapTargetArray(JsonPathExecContext *cxt, JsonPathItem *jsp, } return executeAnyItem - (cxt, jsp, JsonItemBinary(jb).data, found, 1, 1, 1, + (cxt, jsp, JsonItemBinary(jb).data, found, false, 1, 1, 1, false, unwrapElements); } @@ -2212,19 +2254,28 @@ executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, - JsonValueList *found, uint32 level, uint32 first, uint32 last, - bool ignoreStructuralErrors, bool unwrapNext) + JsonValueList *found, bool outPath, uint32 level, uint32 first, + uint32 last, bool ignoreStructuralErrors, bool unwrapNext) { JsonPathExecResult res = jperNotFound; JsonxIterator it; int32 r; JsonItem v; + char *keyStr = NULL; + int keyLen = 0; + JsonValueList items = {0}; + JsonValueList *pitems = found; + bool isObject; check_stack_depth(); if (level > last) return res; + if (pitems && outPath) + pitems = &items; + + isObject = JsonContainerIsObject(jbc); JsonxIteratorInit(&it, jbc, cxt->isJsonb); @@ -2235,8 +2286,13 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, { if (r == WJB_KEY) { + keyStr = JsonItemString(&v).val; + keyLen = JsonItemString(&v).len; r = JsonxIteratorNext(&it, JsonItemJbv(&v), true); Assert(r == WJB_VALUE); + + if (pitems == &items) + JsonValueListClear(pitems); } if (r == WJB_VALUE || r == WJB_ELEM) @@ -2255,11 +2311,11 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, savedIgnoreStructuralErrors = cxt->ignoreStructuralErrors; cxt->ignoreStructuralErrors = true; - res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext); + res = executeItemOptUnwrapTarget(cxt, jsp, &v, pitems, unwrapNext); cxt->ignoreStructuralErrors = savedIgnoreStructuralErrors; } else - res = executeItemOptUnwrapTarget(cxt, jsp, &v, found, unwrapNext); + res = executeItemOptUnwrapTarget(cxt, jsp, &v, pitems, unwrapNext); if (jperIsError(res)) break; @@ -2268,7 +2324,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, break; } else if (found) - JsonValueListAppend(found, copyJsonItem(&v)); + JsonValueListAppend(pitems, copyJsonItem(&v)); else return jperOk; } @@ -2276,7 +2332,7 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, if (level < last && JsonItemIsBinary(&v)) { res = executeAnyItem - (cxt, jsp, JsonItemBinary(&v).data, found, + (cxt, jsp, JsonItemBinary(&v).data, pitems, outPath, level + 1, first, last, ignoreStructuralErrors, unwrapNext); @@ -2287,8 +2343,15 @@ executeAnyItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbContainer *jbc, break; } } + + if (isObject && !JsonValueListIsEmpty(&items) && !jperIsError(res)) + JsonValueListConcat(found, prependKey(keyStr, keyLen, + &items, cxt->isJsonb)); } + if (!isObject && !JsonValueListIsEmpty(&items) && !jperIsError(res)) + appendWrappedItems(found, &items, cxt->isJsonb); + return res; } @@ -3347,6 +3410,24 @@ JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi) jvl->length++; } +static void +JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2) +{ + if (!jvl1->tail) + { + Assert(!jvl1->head); + Assert(!jvl1->length); + *jvl1 = jvl2; + } + else if (jvl2.head) + { + Assert(jvl1->head); + jvl1->tail->next = jvl2.head; + jvl1->tail = jvl2.tail; + jvl1->length += jvl2.length; + } +} + static int JsonValueListLength(const JsonValueList *jvl) { @@ -3733,6 +3814,51 @@ wrapItemsInArray(const JsonValueList *items, bool isJsonb) return push(&ps, WJB_END_ARRAY, NULL); } +static void +appendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb) +{ + JsonbValue *wrapped = wrapItemsInArray(items, isJsonb); + JsonItem *jsi = palloc(sizeof(*jsi)); + + JsonValueListAppend(found, JsonbValueToJsonItem(wrapped, jsi)); +} + +static JsonValueList +prependKey(char *keystr, int keylen, const JsonValueList *items, bool isJsonb) +{ + JsonValueList objs = {0}; + JsonValueListIterator it; + JsonItem *val; + JsonbValue key; + + key.type = jbvString; + key.val.string.val = keystr; + key.val.string.len = keylen; + + JsonValueListInitIterator(items, &it); + + while ((val = JsonValueListNext(items, &it))) + { + JsonbValue *obj; + JsonbValue valbuf; + JsonItem bin; + JsonItem *jsi = palloc(sizeof(*jsi)); + JsonbParseState *ps = NULL; + + if (JsonItemIsObject(val) || JsonItemIsArray(val)) + val = JsonxWrapInBinary(val, &bin, isJsonb); + + pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL); + pushJsonbValue(&ps, WJB_KEY, &key); + pushJsonbValue(&ps, WJB_VALUE, JsonItemToJsonbValue(val, &valbuf)); + obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); + + JsonValueListAppend(&objs, JsonbValueToJsonItem(obj, jsi)); + } + + return objs; +} + static void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item, JsonBaseObjectInfo *base) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 81df3eda1e..e4dd602532 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -59,6 +59,8 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, static JsonPathParseItem *makeItemSequence(List *elems); static JsonPathParseItem *makeItemObject(List *fields); static JsonPathParseItem *makeItemCurrentN(int level); +static JsonPathParseItem *setItemOutPathMode(JsonPathParseItem *jpi); +static List *setItemsOutPathMode(List *items); /* * Bison doesn't allocate anything that needs to live across parser calls, @@ -107,7 +109,7 @@ static JsonPathParseItem *makeItemCurrentN(int level); datetime_template opt_datetime_template expr_seq expr_or_seq object_field -%type accessor_expr expr_list object_field_list +%type accessor_expr accessor_ops expr_list object_field_list %type index_list @@ -236,6 +238,27 @@ accessor_expr: | '(' expr ')' accessor_op { $$ = list_make2($2, $4); } | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); } | accessor_expr accessor_op { $$ = lappend($1, $2); } + | accessor_expr '.' '(' key ')' + { $$ = lappend($1, setItemOutPathMode($4)); } + | accessor_expr '.' '(' key accessor_ops ')' + { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); } + | accessor_expr '.' '(' '*' ')' + { $$ = lappend($1, setItemOutPathMode(makeItemType(jpiAnyKey))); } + | accessor_expr '.' '(' '*' accessor_ops ')' + { $$ = list_concat($1, setItemsOutPathMode(lcons(makeItemType(jpiAnyKey), $5))); } + | accessor_expr '.' '(' array_accessor ')' + { $$ = lappend($1, setItemOutPathMode($4)); } + | accessor_expr '.' '(' array_accessor accessor_ops ')' + { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); } + | accessor_expr '.' '(' any_path ')' + { $$ = lappend($1, setItemOutPathMode($4)); } + | accessor_expr '.' '(' any_path accessor_ops ')' + { $$ = list_concat($1, setItemsOutPathMode(lcons($4, $5))); } + ; + +accessor_ops: + accessor_op { $$ = list_make1($1); } + | accessor_ops accessor_op { $$ = lappend($1, $2); } ; expr: @@ -621,6 +644,24 @@ makeItemObject(List *fields) return v; } +static JsonPathParseItem * +setItemOutPathMode(JsonPathParseItem *jpi) +{ + jpi->flags |= JSPI_OUT_PATH; + return jpi; +} + +static List * +setItemsOutPathMode(List *items) +{ + ListCell *cell; + + foreach(cell, items) + setItemOutPathMode(lfirst(cell)); + + return items; +} + /* * jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is * unavoidable because jsonpath_gram does not create a .h file to export its diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index a415864b03..ad289dce81 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -33,6 +33,9 @@ typedef struct #define JSONPATH_LAX (0x80000000) #define JSONPATH_HDRSZ (offsetof(JsonPath, data)) +/* flags for JsonPathItem */ +#define JSPI_OUT_PATH 0x01 + #define DatumGetJsonPathP(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM(d))) #define DatumGetJsonPathPCopy(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM_COPY(d))) #define PG_GETARG_JSONPATH_P(x) DatumGetJsonPathP(PG_GETARG_DATUM(x)) @@ -210,6 +213,7 @@ typedef struct JsonPathItem } JsonPathItem; #define jspHasNext(jsp) ((jsp)->nextPos > 0) +#define jspOutPath(jsp) (((jsp)->flags & JSPI_OUT_PATH) != 0) extern void jspInit(JsonPathItem *v, JsonPath *js); extern void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index b50107b608..997b6c1528 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2649,3 +2649,49 @@ select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) ------------------ (0 rows) +-- extension: including subpaths into result +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*].b)'); + jsonb_path_query +----------------------------- + {"a": [{"b": 1}, {"b": 2}]} +(1 row) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*]).b'); + jsonb_path_query +------------------ + {"a": [1, 2]} +(1 row) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a.([*].b)'); + jsonb_path_query +---------------------- + [{"b": 1}, {"b": 2}] +(1 row) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].b'); + jsonb_path_query +------------------ + {"a": 1} + {"a": 2} +(2 rows) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a[*].(b)'); + jsonb_path_query +------------------ + {"b": 1} + {"b": 2} +(2 rows) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].(b)'); + jsonb_path_query +------------------ + {"a": {"b": 1}} + {"a": {"b": 2}} +(2 rows) + +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 to 1].b)'); + jsonb_path_query +----------------------------- + {"a": [{"b": 1}, {"b": 2}]} +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 3522c88161..4374191a11 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -598,3 +598,12 @@ select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 2)'); select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ < @1[1]) > 3)'); select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[1]) > @2[0]) > @1[0]) > 2)'); select jsonb_path_query('[2,4,1,5,3]', 'strict $ ? (@[*] ? (@ ? (@ ? (@ < @3[2]) > @2[0]) > @1[0]) > 2)'); + +-- extension: including subpaths into result +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*].b)'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[*]).b'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a.([*].b)'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].b'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a[*].(b)'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].(b)'); +select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 to 1].b)'); From b2a8f4683fd694d5a2b9f9d70f4d7a5534bd71aa Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 22 Dec 2017 15:36:53 +0300 Subject: [PATCH 60/66] Add user-specified SRF state placement --- src/backend/utils/fmgr/funcapi.c | 25 +++++++++++++------------ src/include/funcapi.h | 24 ++++++++++++++++-------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index b7fac5d295..d6bcc27be4 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -50,7 +50,7 @@ static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid); * and error checking */ FuncCallContext * -init_MultiFuncCall(PG_FUNCTION_ARGS) +init_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx) { FuncCallContext *retval; @@ -62,7 +62,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("set-valued function called in context that cannot accept a set"))); - if (fcinfo->flinfo->fn_extra == NULL) + if (*pfuncctx == NULL) { /* * First call @@ -97,7 +97,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS) /* * save the pointer for cross-call use */ - fcinfo->flinfo->fn_extra = retval; + *pfuncctx = retval; /* * Ensure we will get shut down cleanly if the exprcontext is not run @@ -105,7 +105,7 @@ init_MultiFuncCall(PG_FUNCTION_ARGS) */ RegisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall, - PointerGetDatum(fcinfo->flinfo)); + PointerGetDatum(pfuncctx)); } else { @@ -125,9 +125,9 @@ init_MultiFuncCall(PG_FUNCTION_ARGS) * Do Multi-function per-call setup */ FuncCallContext * -per_MultiFuncCall(PG_FUNCTION_ARGS) +per_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx) { - FuncCallContext *retval = (FuncCallContext *) fcinfo->flinfo->fn_extra; + FuncCallContext *retval = *pfuncctx; return retval; } @@ -137,17 +137,18 @@ per_MultiFuncCall(PG_FUNCTION_ARGS) * Clean up after init_MultiFuncCall */ void -end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx) +end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx, + FuncCallContext **pfuncctx) { ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; /* Deregister the shutdown callback */ UnregisterExprContextCallback(rsi->econtext, shutdown_MultiFuncCall, - PointerGetDatum(fcinfo->flinfo)); + PointerGetDatum(pfuncctx)); /* But use it to do the real work */ - shutdown_MultiFuncCall(PointerGetDatum(fcinfo->flinfo)); + shutdown_MultiFuncCall(PointerGetDatum(pfuncctx)); } /* @@ -157,11 +158,11 @@ end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx) static void shutdown_MultiFuncCall(Datum arg) { - FmgrInfo *flinfo = (FmgrInfo *) DatumGetPointer(arg); - FuncCallContext *funcctx = (FuncCallContext *) flinfo->fn_extra; + FuncCallContext **pfuncctx = (FuncCallContext **) DatumGetPointer(arg); + FuncCallContext *funcctx = *pfuncctx; /* unbind from flinfo */ - flinfo->fn_extra = NULL; + *pfuncctx = NULL; /* * Delete context that holds all multi-call data, including the diff --git a/src/include/funcapi.h b/src/include/funcapi.h index ebba8b6f54..1b97159e4a 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -276,15 +276,20 @@ extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple); */ /* from funcapi.c */ -extern FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS); -extern FuncCallContext *per_MultiFuncCall(PG_FUNCTION_ARGS); -extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx); +extern FuncCallContext *init_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx); +extern FuncCallContext *per_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext **pfuncctx); +extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx, FuncCallContext **pfuncctx); -#define SRF_IS_FIRSTCALL() (fcinfo->flinfo->fn_extra == NULL) +#define SRF_DEFAULT_FCTX ((FuncCallContext **) &fcinfo->flinfo->fn_extra) -#define SRF_FIRSTCALL_INIT() init_MultiFuncCall(fcinfo) +#define SRF_IS_FIRSTCALL_EXT(_pfuncctx) (*(_pfuncctx) == NULL) +#define SRF_IS_FIRSTCALL() SRF_IS_FIRSTCALL_EXT(SRF_DEFAULT_FCTX) -#define SRF_PERCALL_SETUP() per_MultiFuncCall(fcinfo) +#define SRF_FIRSTCALL_INIT_EXT(_pfuncctx) init_MultiFuncCall(fcinfo, _pfuncctx) +#define SRF_FIRSTCALL_INIT() SRF_FIRSTCALL_INIT_EXT(SRF_DEFAULT_FCTX) + +#define SRF_PERCALL_SETUP_EXT(_pfuncctx) per_MultiFuncCall(fcinfo, _pfuncctx) +#define SRF_PERCALL_SETUP() SRF_PERCALL_SETUP_EXT(SRF_DEFAULT_FCTX) #define SRF_RETURN_NEXT(_funcctx, _result) \ do { \ @@ -304,15 +309,18 @@ extern void end_MultiFuncCall(PG_FUNCTION_ARGS, FuncCallContext *funcctx); PG_RETURN_NULL(); \ } while (0) -#define SRF_RETURN_DONE(_funcctx) \ +#define SRF_RETURN_DONE_EXT(_funcctx, _pfuncctx) \ do { \ ReturnSetInfo *rsi; \ - end_MultiFuncCall(fcinfo, _funcctx); \ + end_MultiFuncCall(fcinfo, _funcctx, _pfuncctx); \ rsi = (ReturnSetInfo *) fcinfo->resultinfo; \ rsi->isDone = ExprEndResult; \ PG_RETURN_NULL(); \ } while (0) +#define SRF_RETURN_DONE(_funcctx) \ + SRF_RETURN_DONE_EXT(_funcctx, SRF_DEFAULT_FCTX) + /*---------- * Support to ease writing of functions dealing with VARIADIC inputs *---------- From 1110934ce1ffc4550760d7b3252014bd1198253f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 11 Dec 2017 14:48:05 +0300 Subject: [PATCH 61/66] Add jsonpath items with external execution --- src/backend/utils/adt/jsonpath.c | 4 + src/backend/utils/adt/jsonpath_exec.c | 125 +++++++++++++++++++++----- src/include/utils/jsonpath.h | 1 + 3 files changed, 110 insertions(+), 20 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index a1dc6591ce..8a27402c50 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -75,6 +75,7 @@ typedef struct JsonPathContext { StringInfo buf; Jsonb *vars; + int32 id; } JsonPathContext; static Datum jsonPathFromCstring(char *in, int len); @@ -210,6 +211,8 @@ encodeJsonPath(JsonPathParseItem *item, bool lax, int32 sizeEstimation, cxt.buf = &buf; cxt.vars = vars; + cxt.id = 0; + flattenJsonPathParseItem(&cxt, item, 0, false); res = (JsonPath *) buf.data; @@ -217,6 +220,7 @@ encodeJsonPath(JsonPathParseItem *item, bool lax, int32 sizeEstimation, res->header = JSONPATH_VERSION; if (lax) res->header |= JSONPATH_LAX; + res->ext_items_count = cxt.id; return res; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a19019da51..e9a8074396 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -144,6 +144,8 @@ typedef struct JsonPathExecContext * evaluation */ int lastGeneratedObjectId; /* "id" counter for .keyvalue() * evaluation */ + void **cache; + MemoryContext cache_mcxt; int innermostArraySize; /* for LAST array index evaluation */ bool laxMode; /* true for "lax" mode, false for "strict" * mode */ @@ -211,6 +213,12 @@ typedef struct JsonPathUserFuncContext bool silent; /* error suppression flag */ } JsonPathUserFuncContext; +typedef struct JsonpathQueryContext +{ + void *cache; /* jsonpath executor cache */ + FuncCallContext *srfcxt; /* SRF context */ +} JsonpathQueryContext; + /* Structures for JSON_TABLE execution */ typedef struct JsonTableScanState JsonTableScanState; typedef struct JsonTableJoinState JsonTableJoinState; @@ -297,13 +305,16 @@ static float8 float8_mod_error(float8 val1, float8 val2, bool *error); static void freeUserFuncContext(JsonPathUserFuncContext *cxt); static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, - JsonPathUserFuncContext *cxt, bool isJsonb, bool copy); + JsonPathUserFuncContext *cxt, bool isJsonb, bool copy, + void **fn_extra); static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, Jsonx *json, bool isJsonb, bool throwErrors, - JsonValueList *result); + JsonValueList *result, + void **pCache, + MemoryContext cacheCxt); static JsonPathExecResult executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, JsonValueList *found); @@ -475,7 +486,8 @@ static bool JsonTableNextRow(JsonTableScanState *scan, bool isJsonb); static Datum jsonx_path_exists(PG_FUNCTION_ARGS, bool isJsonb) { - JsonPathExecResult res = executeUserFunc(fcinfo, NULL, isJsonb, false); + JsonPathExecResult res = executeUserFunc(fcinfo, NULL, isJsonb, false, + &fcinfo->flinfo->fn_extra); if (jperIsError(res)) PG_RETURN_NULL(); @@ -522,7 +534,8 @@ jsonx_path_match(PG_FUNCTION_ARGS, bool isJsonb) { JsonPathUserFuncContext cxt; - (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false, + &fcinfo->flinfo->fn_extra); freeUserFuncContext(&cxt); @@ -584,22 +597,27 @@ json_path_match_opr(PG_FUNCTION_ARGS) static Datum jsonx_path_query(PG_FUNCTION_ARGS, bool isJsonb) { + JsonpathQueryContext *cxt = fcinfo->flinfo->fn_extra; FuncCallContext *funcctx; List *found; JsonItem *v; ListCell *c; Datum res; - if (SRF_IS_FIRSTCALL()) + if (!cxt) + cxt = fcinfo->flinfo->fn_extra = + MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cxt)); + + if (SRF_IS_FIRSTCALL_EXT(&cxt->srfcxt)) { JsonPathUserFuncContext jspcxt; MemoryContext oldcontext; - funcctx = SRF_FIRSTCALL_INIT(); + funcctx = SRF_FIRSTCALL_INIT_EXT(&cxt->srfcxt); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); /* jsonb and jsonpath arguments are copied into SRF context. */ - (void) executeUserFunc(fcinfo, &jspcxt, isJsonb, true); + (void) executeUserFunc(fcinfo, &jspcxt, isJsonb, true, &cxt->cache); /* * Don't free jspcxt because items in jspcxt.found can reference @@ -610,13 +628,13 @@ jsonx_path_query(PG_FUNCTION_ARGS, bool isJsonb) MemoryContextSwitchTo(oldcontext); } - funcctx = SRF_PERCALL_SETUP(); + funcctx = SRF_PERCALL_SETUP_EXT(&cxt->srfcxt); found = funcctx->user_fctx; c = list_head(found); if (c == NULL) - SRF_RETURN_DONE(funcctx); + SRF_RETURN_DONE_EXT(funcctx, &cxt->srfcxt); v = lfirst(c); funcctx->user_fctx = list_delete_first(found); @@ -651,7 +669,8 @@ jsonx_path_query_array(PG_FUNCTION_ARGS, bool isJsonb) JsonPathUserFuncContext cxt; Datum res; - (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false, + &fcinfo->flinfo->fn_extra); res = JsonbValueToJsonxDatum(wrapItemsInArray(&cxt.found, isJsonb), isJsonb); @@ -683,7 +702,8 @@ jsonx_path_query_first(PG_FUNCTION_ARGS, bool isJsonb) JsonPathUserFuncContext cxt; Datum res; - (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false, + &fcinfo->flinfo->fn_extra); if (JsonValueListLength(&cxt.found) >= 1) res = JsonItemToJsonxDatum(JsonValueListHead(&cxt.found), isJsonb); @@ -721,7 +741,8 @@ jsonx_path_query_first_text(PG_FUNCTION_ARGS, bool isJsonb) JsonPathUserFuncContext cxt; text *txt; - (void) executeUserFunc(fcinfo, &cxt, isJsonb, false); + (void) executeUserFunc(fcinfo, &cxt, isJsonb, false, + &fcinfo->flinfo->fn_extra); if (JsonValueListLength(&cxt.found) >= 1) txt = JsonItemUnquoteText(JsonValueListHead(&cxt.found), isJsonb); @@ -771,7 +792,7 @@ freeUserFuncContext(JsonPathUserFuncContext *cxt) */ static JsonPathExecResult executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, - bool isJsonb, bool copy) + bool isJsonb, bool copy, void **fn_extra) { Datum js_toasted = PG_GETARG_DATUM(0); struct varlena *js_detoasted = copy ? @@ -809,7 +830,8 @@ executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, } res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonx, - js, isJsonb, !silent, cxt ? &cxt->found : NULL); + js, isJsonb, !silent, cxt ? &cxt->found : NULL, + fn_extra, fcinfo->flinfo->fn_mcxt); if (!cxt && !copy) { @@ -854,7 +876,7 @@ executeUserFunc(FunctionCallInfo fcinfo, JsonPathUserFuncContext *cxt, static JsonPathExecResult executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, Jsonx *json, bool isJsonb, bool throwErrors, - JsonValueList *result) + JsonValueList *result, void **pCache, MemoryContext cacheCxt) { JsonPathExecContext cxt; JsonPathExecResult res; @@ -863,6 +885,66 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, JsonbValue *jbv = JsonItemJbv(&jsi); JsonItemStackEntry root; + if (path->ext_items_count) + { + if (pCache) + { + struct + { + JsonPath *path; + void **cache; + } *cache = *pCache; + + if (!cache) + cache = *pCache = MemoryContextAllocZero(cacheCxt, sizeof(*cache)); + + if (cache->path && + (VARSIZE(path) != VARSIZE(cache->path) || + memcmp(path, cache->path, VARSIZE(path)))) + { + /* invalidate cache TODO optimize */ + cache->path = NULL; + + if (cache->cache) + { + pfree(cache->cache); + cache->cache = NULL; + } + } + + if (cache->path) + { + Assert(cache->cache); + } + else + { + Assert(!cache->cache); + + cache->path = MemoryContextAlloc(cacheCxt, VARSIZE(path)); + memcpy(cache->path, path, VARSIZE(path)); + + cache->cache = MemoryContextAllocZero(cacheCxt, + sizeof(cxt.cache[0]) * + path->ext_items_count); + } + + cxt.cache = cache->cache; + cxt.cache_mcxt = cacheCxt; + + path = cache->path; /* use cached jsonpath value */ + } + else + { + cxt.cache = palloc0(sizeof(cxt.cache[0]) * path->ext_items_count); + cxt.cache_mcxt = CurrentMemoryContext; + } + } + else + { + cxt.cache = NULL; + cxt.cache_mcxt = NULL; + } + jspInit(&jsp, path); if (isJsonb) @@ -4169,7 +4251,8 @@ JsonPathExists(Datum jb, JsonPath *jp, List *vars, bool isJsonb, { Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonPathExecResult res = executeJsonPath(jp, vars, EvalJsonPathVar, - js, isJsonb, !error, NULL); + js, isJsonb, !error, + NULL, NULL, NULL); Assert(error || !jperIsError(res)); @@ -4190,7 +4273,8 @@ JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty, JsonPathExecResult res PG_USED_FOR_ASSERTS_ONLY; int count; - res = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error, &found); + res = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error, + &found, NULL, NULL); Assert(error || !jperIsError(res)); @@ -4259,11 +4343,11 @@ JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars, Jsonx *js = DatumGetJsonxP(jb, isJsonb); JsonItem *res; JsonValueList found = { 0 }; - JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY; + JsonPathExecResult jper; int count; jper = executeJsonPath(jp, vars, EvalJsonPathVar, js, isJsonb, !error, - &found); + &found, NULL, NULL); Assert(error || !jperIsError(jper)); @@ -4615,7 +4699,8 @@ JsonTableResetContextItem(JsonTableScanState *scan, Datum item, bool isJsonb) oldcxt = MemoryContextSwitchTo(scan->mcxt); res = executeJsonPath(scan->path, scan->args, EvalJsonPathVar, js, isJsonb, - scan->errorOnError, &scan->found); + scan->errorOnError, &scan->found, + NULL /* FIXME */, NULL); MemoryContextSwitchTo(oldcxt); diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index ad289dce81..b715cc3772 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -26,6 +26,7 @@ typedef struct { int32 vl_len_; /* varlena header (do not touch directly!) */ uint32 header; /* version and flags (see below) */ + uint32 ext_items_count; /* number of items that need cache for external execution */ char data[FLEXIBLE_ARRAY_MEMBER]; } JsonPath; From 74967bae579f1ffd93c10c259240584bba718322 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 18 Dec 2017 18:38:28 +0300 Subject: [PATCH 62/66] Export jsonpath execution functions and structures --- src/backend/utils/adt/jsonpath_exec.c | 240 +++++++------------------- src/include/utils/jsonpath.h | 168 ++++++++++++++++++ 2 files changed, 230 insertions(+), 178 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index e9a8074396..49b4f49425 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -82,82 +82,6 @@ #include "utils/timestamp.h" #include "utils/varlena.h" - -typedef union Jsonx -{ - Jsonb jb; - Json js; -} Jsonx; - -#define DatumGetJsonxP(datum, isJsonb) \ - ((isJsonb) ? (Jsonx *) DatumGetJsonbP(datum) : (Jsonx *) DatumGetJsonP(datum)) - -typedef JsonbContainer JsonxContainer; - -typedef struct JsonxIterator -{ - bool isJsonb; - union - { - JsonbIterator *jb; - JsonIterator *js; - } it; -} JsonxIterator; - -/* - * Represents "base object" and it's "id" for .keyvalue() evaluation. - */ -typedef struct JsonBaseObjectInfo -{ - JsonxContainer *jbc; - int id; -} JsonBaseObjectInfo; - -/* - * Special data structure representing stack of current items. We use it - * instead of regular list in order to evade extra memory allocation. These - * items are always allocated in local variables. - */ -typedef struct JsonItemStackEntry -{ - JsonBaseObjectInfo base; - JsonItem *item; - struct JsonItemStackEntry *parent; -} JsonItemStackEntry; - -typedef JsonItemStackEntry *JsonItemStack; - -typedef int (*JsonPathVarCallback) (void *vars, bool isJsonb, - char *varName, int varNameLen, - JsonItem *val, JsonbValue *baseObject); - -/* - * Context of jsonpath execution. - */ -typedef struct JsonPathExecContext -{ - void *vars; /* variables to substitute into jsonpath */ - JsonPathVarCallback getVar; - JsonItem *root; /* for $ evaluation */ - JsonItemStack stack; /* for @ evaluation */ - JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() - * evaluation */ - int lastGeneratedObjectId; /* "id" counter for .keyvalue() - * evaluation */ - void **cache; - MemoryContext cache_mcxt; - int innermostArraySize; /* for LAST array index evaluation */ - bool laxMode; /* true for "lax" mode, false for "strict" - * mode */ - bool ignoreStructuralErrors; /* with "true" structural errors such - * as absence of required json item or - * unexpected json item type are - * ignored */ - bool throwErrors; /* with "false" all suppressible errors are - * suppressed */ - bool isJsonb; -} JsonPathExecContext; - /* Context for LIKE_REGEX execution. */ typedef struct JsonLikeRegexContext { @@ -165,39 +89,6 @@ typedef struct JsonLikeRegexContext int cflags; } JsonLikeRegexContext; -/* Result of jsonpath predicate evaluation */ -typedef enum JsonPathBool -{ - jpbFalse = 0, - jpbTrue = 1, - jpbUnknown = 2 -} JsonPathBool; - -/* Result of jsonpath expression evaluation */ -typedef enum JsonPathExecResult -{ - jperOk = 0, - jperNotFound = 1, - jperError = 2 -} JsonPathExecResult; - -#define jperIsError(jper) ((jper) == jperError) - -/* - * List of SQL/JSON items with shortcut for single-value list. - */ -typedef struct JsonValueList -{ - JsonItem *head; - JsonItem *tail; - int length; -} JsonValueList; - -typedef struct JsonValueListIterator -{ - JsonItem *next; -} JsonValueListIterator; - /* * Context for execution of * jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent boolean]) user functions. @@ -273,13 +164,6 @@ typedef struct JsonTableContext bool isJsonb; } JsonTableContext; -/* strict/lax flags is decomposed into four [un]wrap/error flags */ -#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode) -#define jspAutoUnwrap(cxt) ((cxt)->laxMode) -#define jspAutoWrap(cxt) ((cxt)->laxMode) -#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors) -#define jspThrowErrors(cxt) ((cxt)->throwErrors) - /* Convenience macro: return or throw error depending on context */ #define RETURN_ERROR(throw_error) \ do { \ @@ -389,10 +273,8 @@ static void getJsonPathVariable(JsonPathExecContext *cxt, static int getJsonPathVariableFromJsonx(void *varsJsonb, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); -static int JsonxArraySize(JsonItem *jb, bool isJsonb); static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p); -static JsonPathBool compareItems(int32 op, JsonItem *jb1, JsonItem *jb2); static int compareNumeric(Numeric a, Numeric b); static void JsonItemInitNull(JsonItem *item); @@ -405,8 +287,6 @@ static void JsonItemInitString(JsonItem *item, char *str, int len); static void JsonItemInitDatetime(JsonItem *item, Datum val, Oid typid, int32 typmod, int tz); -static JsonItem *copyJsonItem(JsonItem *src); -static JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); static const char *JsonItemTypeName(JsonItem *jsi); static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, @@ -414,25 +294,12 @@ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, int32 *index); static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt, JsonItem *jsi, int32 id); -static void JsonValueListClear(JsonValueList *jvl); -static void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv); -static void JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2); -static int JsonValueListLength(const JsonValueList *jvl); -static bool JsonValueListIsEmpty(JsonValueList *jvl); -static JsonItem *JsonValueListHead(JsonValueList *jvl); -static List *JsonValueListGetList(JsonValueList *jvl); -static void JsonValueListInitIterator(const JsonValueList *jvl, - JsonValueListIterator *it); -static JsonItem *JsonValueListNext(const JsonValueList *jvl, - JsonValueListIterator *it); -static int JsonbType(JsonItem *jb); static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb); static inline JsonbValue *JsonInitBinary(JsonbValue *jbv, Json *js); static JsonItem *getScalar(JsonItem *scalar, enum jbvType type); static JsonItem *getNumber(JsonItem *scalar); static bool convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num); static JsonbValue *wrapItemsInArray(const JsonValueList *items, bool isJsonb); -static JsonItem *wrapItem(JsonItem *jbv, bool isJsonb); static text *JsonItemUnquoteText(JsonItem *jsi, bool isJsonb); static JsonItem *wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb); @@ -446,10 +313,6 @@ static JsonItem *getJsonObjectKey(JsonItem *jb, char *keystr, int keylen, static JsonItem *getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, JsonItem *res); -static void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, - bool isJsonb); -static JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, - bool skipNested); static JsonbValue *JsonItemToJsonbValue(JsonItem *jsi, JsonbValue *jbv); static Jsonx *JsonbValueToJsonx(JsonbValue *jbv, bool isJsonb); @@ -460,10 +323,6 @@ static int compareDatetime(Datum val1, Oid typid1, int tz1, Datum val2, Oid typid2, int tz2, bool *error); -static void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, - JsonItem *item, JsonBaseObjectInfo *base); -static void popJsonItem(JsonItemStack *stack); - static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, Node *plan, JsonTableScanState *parent); static bool JsonTableNextRow(JsonTableScanState *scan, bool isJsonb); @@ -1007,6 +866,13 @@ executeItem(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItemOptUnwrapTarget(cxt, jsp, jb, found, jspAutoUnwrap(cxt)); } +JsonPathExecResult +jspExecuteItem(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *found) +{ + return executeItem(cxt, jsp, jb, found); +} + /* * Main jsonpath executor function: walks on jsonpath structure, finds * relevant parts of jsonb and evaluates expressions over them. @@ -2310,6 +2176,30 @@ executeBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, } } +/* + * Execute nested expression pushing current SQL/JSON item onto the stack. + */ +static inline JsonPathExecResult +executeItemNested(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *found) +{ + JsonItemStackEntry current; + JsonPathExecResult res; + + pushJsonItem(&cxt->stack, ¤t, jb, &cxt->baseObject); + res = executeItem(cxt, jsp, jb, found); + popJsonItem(&cxt->stack); + + return res; +} + +JsonPathExecResult +jspExecuteItemNested(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *found) +{ + return executeItemNested(cxt, jsp, jb, found); +} + /* * Execute nested (filters etc.) boolean expression pushing current SQL/JSON * item onto the stack. @@ -3137,7 +3027,7 @@ getJsonPathVariableFromJsonx(void *varsJsonx, bool isJsonb, /* * Returns the size of an array item, or -1 if item is not an array. */ -static int +int JsonxArraySize(JsonItem *jb, bool isJsonb) { if (JsonItemIsArray(jb)) @@ -3168,14 +3058,14 @@ JsonxArraySize(JsonItem *jb, bool isJsonb) static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p) { - return compareItems(cmp->type, lv, rv); + return jspCompareItems(cmp->type, lv, rv); } /* * Compare two SQL/JSON items using comparison operation 'op'. */ -static JsonPathBool -compareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) +JsonPathBool +jspCompareItems(int32 op, JsonItem *jsi1, JsonItem *jsi2) { JsonbValue *jb1 = JsonItemJbv(jsi1); JsonbValue *jb2 = JsonItemJbv(jsi2); @@ -3303,7 +3193,7 @@ compareNumeric(Numeric a, Numeric b) NumericGetDatum(b))); } -static JsonItem * +JsonItem * copyJsonItem(JsonItem *src) { JsonItem *dst = palloc(sizeof(*dst)); @@ -3313,7 +3203,7 @@ copyJsonItem(JsonItem *src) return dst; } -static JsonItem * +JsonItem * JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi) { *JsonItemJbv(jsi) = *jbv; @@ -3465,7 +3355,7 @@ setBaseObject(JsonPathExecContext *cxt, JsonItem *jbv, int32 id) return baseObject; } -static void +void JsonValueListClear(JsonValueList *jvl) { jvl->head = NULL; @@ -3473,7 +3363,7 @@ JsonValueListClear(JsonValueList *jvl) jvl->length = 0; } -static void +void JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi) { jsi->next = NULL; @@ -3492,7 +3382,7 @@ JsonValueListAppend(JsonValueList *jvl, JsonItem *jsi) jvl->length++; } -static void +void JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2) { if (!jvl1->tail) @@ -3510,25 +3400,7 @@ JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2) } } -static int -JsonValueListLength(const JsonValueList *jvl) -{ - return jvl->length; -} - -static bool -JsonValueListIsEmpty(JsonValueList *jvl) -{ - return !jvl->length; -} - -static JsonItem * -JsonValueListHead(JsonValueList *jvl) -{ - return jvl->head; -} - -static List * +List * JsonValueListGetList(JsonValueList *jvl) { List *list = NIL; @@ -3540,7 +3412,7 @@ JsonValueListGetList(JsonValueList *jvl) return list; } -static void +void JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) { it->next = jvl->head; @@ -3549,7 +3421,7 @@ JsonValueListInitIterator(const JsonValueList *jvl, JsonValueListIterator *it) /* * Get the next item from the sequence advancing iterator. */ -static JsonItem * +JsonItem * JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) { JsonItem *result = it->next; @@ -3624,7 +3496,7 @@ wrapJsonObjectOrArray(JsonItem *js, JsonItem *buf, bool isJsonb) /* * Returns jbv* type of of JsonbValue. Note, it never returns jbvBinary as is. */ -static int +int JsonbType(JsonItem *jb) { int type = JsonItemGetType(jb); @@ -3750,7 +3622,7 @@ getJsonArrayElement(JsonItem *jb, uint32 index, bool isJsonb, JsonItem *res) return elem ? res : NULL; } -static inline void +void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb) { it->isJsonb = isJsonb; @@ -3760,7 +3632,7 @@ JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, bool isJsonb) it->it.js = JsonIteratorInit((JsonContainer *) jxc); } -static JsonbIteratorToken +JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, bool skipNested) { return it->isJsonb ? @@ -3836,8 +3708,8 @@ convertJsonDoubleToNumeric(JsonItem *dbl, JsonItem *num) * Wrap a non-array SQL/JSON item into an array for applying array subscription * path steps in lax mode. */ -static JsonItem * -wrapItem(JsonItem *jsi, bool isJsonb) +JsonItem * +JsonWrapItemInArray(JsonItem *jsi, bool isJsonb) { JsonbParseState *ps = NULL; JsonItem jsibuf; @@ -3896,6 +3768,12 @@ wrapItemsInArray(const JsonValueList *items, bool isJsonb) return push(&ps, WJB_END_ARRAY, NULL); } +JsonbValue * +JsonWrapItemsInArray(const JsonValueList *items, bool isJsonb) +{ + return wrapItemsInArray(items, isJsonb); +} + static void appendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb) { @@ -3905,6 +3783,12 @@ appendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb) JsonValueListAppend(found, JsonbValueToJsonItem(wrapped, jsi)); } +void +JsonAppendWrappedItems(JsonValueList *found, JsonValueList *items, bool isJsonb) +{ + return appendWrappedItems(found, items, isJsonb); +} + static JsonValueList prependKey(char *keystr, int keylen, const JsonValueList *items, bool isJsonb) { @@ -3941,7 +3825,7 @@ prependKey(char *keystr, int keylen, const JsonValueList *items, bool isJsonb) return objs; } -static void +void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item, JsonBaseObjectInfo *base) { @@ -3951,7 +3835,7 @@ pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonItem *item, *stack = entry; } -static void +void popJsonItem(JsonItemStack *stack) { *stack = (*stack)->parent; diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index b715cc3772..08452158e8 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -415,6 +415,123 @@ typedef struct JsonItem #define JsonItemIsNumber(jsi) (JsonItemIsNumeric(jsi) || \ JsonItemIsDouble(jsi)) +typedef union Jsonx +{ + Jsonb jb; + Json js; +} Jsonx; + +#define DatumGetJsonxP(datum, isJsonb) \ + ((isJsonb) ? (Jsonx *) DatumGetJsonbP(datum) : (Jsonx *) DatumGetJsonP(datum)) + +typedef JsonbContainer JsonxContainer; + +typedef struct JsonxIterator +{ + bool isJsonb; + union + { + JsonbIterator *jb; + JsonIterator *js; + } it; +} JsonxIterator; + +/* + * Represents "base object" and it's "id" for .keyvalue() evaluation. + */ +typedef struct JsonBaseObjectInfo +{ + JsonxContainer *jbc; + int id; +} JsonBaseObjectInfo; + +/* + * Special data structure representing stack of current items. We use it + * instead of regular list in order to evade extra memory allocation. These + * items are always allocated in local variables. + */ +typedef struct JsonItemStackEntry +{ + JsonBaseObjectInfo base; + JsonItem *item; + struct JsonItemStackEntry *parent; +} JsonItemStackEntry; + +typedef JsonItemStackEntry *JsonItemStack; + +typedef int (*JsonPathVarCallback) (void *vars, bool isJsonb, + char *varName, int varNameLen, + JsonItem *val, JsonbValue *baseObject); + +/* + * Context of jsonpath execution. + */ +typedef struct JsonPathExecContext +{ + void *vars; /* variables to substitute into jsonpath */ + JsonPathVarCallback getVar; + JsonItem *root; /* for $ evaluation */ + JsonItemStack stack; /* for @ evaluation */ + JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() + * evaluation */ + int lastGeneratedObjectId; /* "id" counter for .keyvalue() + * evaluation */ + void **cache; + MemoryContext cache_mcxt; + int innermostArraySize; /* for LAST array index evaluation */ + bool laxMode; /* true for "lax" mode, false for "strict" + * mode */ + bool ignoreStructuralErrors; /* with "true" structural errors such + * as absence of required json item or + * unexpected json item type are + * ignored */ + bool throwErrors; /* with "false" all suppressible errors are + * suppressed */ + bool isJsonb; +} JsonPathExecContext; + +/* strict/lax flags is decomposed into four [un]wrap/error flags */ +#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode) +#define jspAutoUnwrap(cxt) ((cxt)->laxMode) +#define jspAutoWrap(cxt) ((cxt)->laxMode) +#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors) +#define jspThrowErrors(cxt) ((cxt)->throwErrors) + +/* Result of jsonpath predicate evaluation */ +typedef enum JsonPathBool +{ + jpbFalse = 0, + jpbTrue = 1, + jpbUnknown = 2 +} JsonPathBool; + +/* Result of jsonpath expression evaluation */ +typedef enum JsonPathExecResult +{ + jperOk = 0, + jperNotFound = 1, + jperError = 2 +} JsonPathExecResult; + +#define jperIsError(jper) ((jper) == jperError) + +/* + * List of SQL/JSON items with shortcut for single-value list. + */ +typedef struct JsonValueList +{ + JsonItem *head; + JsonItem *tail; + int length; +} JsonValueList; + +typedef struct JsonValueListIterator +{ + JsonItem *next; +} JsonValueListIterator; + + +extern JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi); extern Jsonb *JsonItemToJsonb(JsonItem *jsi); extern Json *JsonItemToJson(JsonItem *jsi); extern void JsonItemFromDatum(Datum val, Oid typid, int32 typmod, @@ -435,4 +552,55 @@ extern int EvalJsonPathVar(void *vars, bool isJsonb, char *varName, extern const TableFuncRoutine JsonTableRoutine; extern const TableFuncRoutine JsonbTableRoutine; +extern int JsonbType(JsonItem *jb); +extern int JsonxArraySize(JsonItem *jb, bool isJsonb); + +extern JsonItem *copyJsonItem(JsonItem *src); +extern JsonItem *JsonWrapItemInArray(JsonItem *jbv, bool isJsonb); +extern JsonbValue *JsonWrapItemsInArray(const JsonValueList *items, + bool isJsonb); +extern void JsonAppendWrappedItems(JsonValueList *found, JsonValueList *items, + bool isJsonb); + +extern void pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, + JsonItem *item, JsonBaseObjectInfo *base); +extern void popJsonItem(JsonItemStack *stack); + +#define JsonValueListLength(jvl) ((jvl)->length) +#define JsonValueListIsEmpty(jvl) (!(jvl)->length) +#define JsonValueListHead(jvl) ((jvl)->head) +extern void JsonValueListClear(JsonValueList *jvl); +extern void JsonValueListAppend(JsonValueList *jvl, JsonItem *jbv); +extern List *JsonValueListGetList(JsonValueList *jvl); +extern void JsonValueListInitIterator(const JsonValueList *jvl, + JsonValueListIterator *it); +extern JsonItem *JsonValueListNext(const JsonValueList *jvl, + JsonValueListIterator *it); +extern void JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2); +extern void JsonxIteratorInit(JsonxIterator *it, JsonxContainer *jxc, + bool isJsonb); +extern JsonbIteratorToken JsonxIteratorNext(JsonxIterator *it, JsonbValue *jbv, + bool skipNested); + +extern JsonPathExecResult jspExecuteItem(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonItem *jb, + JsonValueList *found); +extern JsonPathExecResult jspExecuteItemNested(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonItem *jb, + JsonValueList *found); +extern JsonPathBool jspCompareItems(int32 op, JsonItem *jb1, JsonItem *jb2); + +/* Standard error message for SQL/JSON errors */ +#define ERRMSG_JSON_ARRAY_NOT_FOUND "SQL/JSON array not found" +#define ERRMSG_JSON_OBJECT_NOT_FOUND "SQL/JSON object not found" +#define ERRMSG_JSON_MEMBER_NOT_FOUND "SQL/JSON member not found" +#define ERRMSG_JSON_NUMBER_NOT_FOUND "SQL/JSON number not found" +#define ERRMSG_JSON_SCALAR_REQUIRED "SQL/JSON scalar required" +#define ERRMSG_MORE_THAN_ONE_JSON_ITEM "more than one SQL/JSON item" +#define ERRMSG_SINGLETON_JSON_ITEM_REQUIRED "singleton SQL/JSON item required" +#define ERRMSG_NON_NUMERIC_JSON_ITEM "non-numeric SQL/JSON item" +#define ERRMSG_INVALID_JSON_SUBSCRIPT "invalid SQL/JSON subscript" +#define ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION \ + "invalid argument for SQL/JSON datetime function" + #endif From fd9c5a8d70448f2b75f84942616837cc976c2cbf Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 22 Dec 2017 15:31:22 +0300 Subject: [PATCH 63/66] Add jsonpath lambda expressions --- src/backend/utils/adt/jsonpath.c | 131 ++++++++++- src/backend/utils/adt/jsonpath_exec.c | 232 ++++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 71 +++++- src/backend/utils/adt/jsonpath_scan.l | 4 +- src/include/utils/jsonpath.h | 33 +++ src/test/regress/expected/json_sqljson.out | 2 +- src/test/regress/expected/jsonb_sqljson.out | 2 +- 7 files changed, 468 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 8a27402c50..613adfa197 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -328,6 +328,7 @@ copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level, case jpiKey: case jpiString: case jpiVariable: + case jpiArgument: { int32 len; char *data = jspGetString(item, &len); @@ -542,6 +543,39 @@ copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level, } break; + case jpiLambda: + { + JsonPathItem arg; + int32 nparams = item->content.lambda.nparams; + int offset; + int32 elempos; + int32 i; + + /* assign cache id */ + appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id)); + ++cxt->id; + + appendBinaryStringInfo(buf, (char *) &nparams, sizeof(nparams)); + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * (nparams + 1)); + + for (i = 0; i < nparams; i++) + { + jspGetLambdaParam(item, i, &arg); + elempos = copyJsonPathItem(cxt, &arg, level, NULL, NULL); + *(int32 *) &buf->data[offset] = elempos - pos; + offset += sizeof(int32); + } + + jspGetLambdaExpr(item, &arg); + elempos = copyJsonPathItem(cxt, &arg, level, NULL, NULL); + + *(int32 *) &buf->data[offset] = elempos - pos; + offset += sizeof(int32); + } + break; + default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -608,6 +642,7 @@ flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item, case jpiString: case jpiVariable: case jpiKey: + case jpiArgument: appendBinaryStringInfo(buf, (char *) &item->value.string.len, sizeof(item->value.string.len)); appendBinaryStringInfo(buf, item->value.string.val, @@ -701,6 +736,38 @@ flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item, *(int32 *) (buf->data + arg) = chld - pos; } break; + case jpiLambda: + { + int32 nelems = list_length(item->value.lambda.params); + ListCell *lc; + int offset; + int32 elempos; + + /* assign cache id */ + appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id)); + ++cxt->id; + + appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * (nelems + 1)); + + foreach(lc, item->value.lambda.params) + { + elempos = flattenJsonPathParseItem(cxt, lfirst(lc), + nestingLevel, + insideArraySubscript); + *(int32 *) &buf->data[offset] = elempos - pos; + offset += sizeof(int32); + } + + elempos = flattenJsonPathParseItem(cxt, item->value.lambda.expr, + nestingLevel, + insideArraySubscript); + *(int32 *) &buf->data[offset] = elempos - pos; + offset += sizeof(int32); + } + break; case jpiNull: break; case jpiRoot: @@ -917,6 +984,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, appendStringInfoChar(buf, '$'); escape_json(buf, jspGetString(v, NULL)); break; + case jpiArgument: + appendStringInfoString(buf, jspGetString(v, NULL)); + break; case jpiNumeric: appendStringInfoString(buf, DatumGetCString(DirectFunctionCall1(numeric_out, @@ -1183,6 +1253,31 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, appendStringInfoChar(buf, '}'); break; + case jpiLambda: + if (printBracketes || jspHasNext(v)) + appendStringInfoChar(buf, '('); + + appendStringInfoChar(buf, '('); + + for (i = 0; i < v->content.lambda.nparams; i++) + { + JsonPathItem elem; + + if (i) + appendBinaryStringInfo(buf, ", ", 2); + + jspGetLambdaParam(v, i, &elem); + printJsonPathItem(buf, &elem, false, false); + } + + appendStringInfoString(buf, ") => "); + + jspGetLambdaExpr(v, &elem); + printJsonPathItem(buf, &elem, false, false); + + if (printBracketes || jspHasNext(v)) + appendStringInfoChar(buf, ')'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -1350,6 +1445,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiKey: case jpiString: case jpiVariable: + case jpiArgument: read_int32(v->content.value.datalen, base, pos); /* FALLTHROUGH */ case jpiNumeric: @@ -1380,6 +1476,13 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32(v->content.like_regex.patternlen, base, pos); v->content.like_regex.pattern = base + pos; break; + case jpiLambda: + read_int32(v->content.lambda.id, base, pos); + read_int32(v->content.lambda.nparams, base, pos); + read_int32_n(v->content.lambda.params, base, pos, + v->content.lambda.nparams); + read_int32(v->content.lambda.expr, base, pos); + break; case jpiNot: case jpiExists: case jpiIsUnknown: @@ -1476,7 +1579,9 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiStartsWith || v->type == jpiSequence || v->type == jpiArray || - v->type == jpiObject); + v->type == jpiObject || + v->type == jpiLambda || + v->type == jpiArgument); if (a) jspInitByBuffer(a, v->base, v->nextPos); @@ -1551,7 +1656,8 @@ jspGetString(JsonPathItem *v, int32 *len) { Assert(v->type == jpiKey || v->type == jpiString || - v->type == jpiVariable); + v->type == jpiVariable || + v->type == jpiArgument); if (len) *len = v->content.value.datalen; @@ -1590,6 +1696,27 @@ jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val) jspInitByBuffer(val, v->base, v->content.object.fields[i].val); } +JsonPathItem * +jspGetLambdaParam(JsonPathItem *lambda, int index, JsonPathItem *arg) +{ + Assert(lambda->type == jpiLambda); + Assert(index < lambda->content.lambda.nparams); + + jspInitByBuffer(arg, lambda->base, lambda->content.lambda.params[index]); + + return arg; +} + +JsonPathItem * +jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr) +{ + Assert(lambda->type == jpiLambda); + + jspInitByBuffer(expr, lambda->base, lambda->content.lambda.expr); + + return expr; +} + static void checkJsonPathArgsMismatch(JsonPath *jp1, JsonPath *jp2) { diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 49b4f49425..536cc524b6 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -89,6 +89,26 @@ typedef struct JsonLikeRegexContext int cflags; } JsonLikeRegexContext; +typedef struct JsonLambdaVar +{ + char *name; + JsonItem *val; +} JsonLambdaVar; + +typedef struct JsonLambdaVars +{ + int nvars; + JsonLambdaVar *vars; + void *parentVars; + JsonPathVarCallback parentGetVar; +} JsonLambdaVars; + +typedef union JsonLambdaCache +{ + JsonLambdaArg *args; + JsonLambdaVars vars; +} JsonLambdaCache; + /* * Context for execution of * jsonb_path_*(jsonb, jsonpath [, vars jsonb, silent boolean]) user functions. @@ -1417,6 +1437,7 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiNumeric: case jpiString: case jpiVariable: + case jpiArgument: { JsonItem vbuf; JsonItem *v; @@ -1887,6 +1908,10 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } + case jpiLambda: + elog(ERROR, "jsonpath lambda expression cannot be executed directly"); + break; + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } @@ -2218,6 +2243,187 @@ executeNestedBoolItem(JsonPathExecContext *cxt, JsonPathItem *jsp, return res; } +static int +getLambdaVar(void *cxt, bool isJsonb, char *varName, int varNameLen, + JsonItem *val, JsonbValue *baseObject) +{ + JsonLambdaVars *vars = cxt; + int i = 0; + + for (i = 0; i < vars->nvars; i++) + { + JsonLambdaVar *var = &vars->vars[i]; + + if (!strncmp(var->name, varName, varNameLen)) + { + *val = *var->val; + return 0; /* FIXME */ + } + } + + return vars->parentGetVar(vars->parentVars, isJsonb, varName, varNameLen, + val, baseObject); +} + +static JsonPathExecResult +recursiveExecuteLambdaVars(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *found, + JsonItem **params, int nparams, void **pcache) +{ + JsonLambdaCache *cache = *pcache; + JsonPathExecResult res; + int i; + + if (nparams > 0 && !cache) + { + MemoryContext oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt); + + cache = *pcache = palloc0(sizeof(*cache)); + cache->vars.vars = palloc(sizeof(*cache->vars.vars) * nparams); + cache->vars.nvars = nparams; + cache->vars.parentVars = NULL; + cache->vars.parentGetVar = NULL; + + for (i = 0; i < nparams; i++) + { + JsonLambdaVar *var = &cache->vars.vars[i]; + char varname[20]; + + snprintf(varname, sizeof(varname), "%d", i + 1); + + var->name = pstrdup(varname); + var->val = NULL; + } + + MemoryContextSwitchTo(oldcontext); + } + + for (i = 0; i < nparams; i++) + cache->vars.vars[i].val = params[i]; + + cache->vars.parentVars = cxt->vars; + cache->vars.parentGetVar = cxt->getVar; + cxt->vars = cache; + cxt->getVar = getLambdaVar; + + if (found) + { + res = executeItem(cxt, jsp, jb, found); + } + else + { + JsonPathBool ok = executeBoolItem(cxt, jsp, jb, false); + + if (ok == jpbUnknown) + { + cxt->vars = cache->vars.parentVars; + cxt->getVar = cache->vars.parentGetVar; + + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NO_JSON_ITEM), + errmsg(ERRMSG_NO_JSON_ITEM)))); /* FIXME */ + } + res = ok == jpbTrue ? jperOk : jperNotFound; + } + + cxt->vars = cache->vars.parentVars; + cxt->getVar = cache->vars.parentGetVar; + + return res; +} + +static inline JsonPathExecResult +recursiveExecuteLambdaExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *found, + JsonItem **params, int nparams, void **pcache) +{ + JsonPathItem expr; + JsonPathExecResult res; + JsonLambdaCache *cache = *pcache; + JsonLambdaArg *oldargs; + int i; + + if (jsp->content.lambda.nparams > nparams) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("jsonpath lambda arguments mismatch: expected %d but given %d", + jsp->content.lambda.nparams, nparams))); + + if (jsp->content.lambda.nparams > 0 && !cache) + { + MemoryContext oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt); + + cache = *pcache = palloc0(sizeof(*cache)); + cache->args = palloc(sizeof(cache->args[0]) * + jsp->content.lambda.nparams); + + for (i = 0; i < jsp->content.lambda.nparams; i++) + { + JsonLambdaArg *arg = &cache->args[i]; + JsonPathItem argname; + + jspGetLambdaParam(jsp, i, &argname); + + if (argname.type != jpiArgument) + elog(ERROR, "invalid jsonpath lambda argument item type: %d", + argname.type); + + arg->name = jspGetString(&argname, &arg->namelen); + arg->val = NULL; + arg->next = arg + 1; + } + + MemoryContextSwitchTo(oldcontext); + } + + oldargs = cxt->args; + + if (jsp->content.lambda.nparams > 0) + { + for (i = 0; i < jsp->content.lambda.nparams; i++) + cache->args[i].val = params[i]; + + cache->args[jsp->content.lambda.nparams - 1].next = oldargs; + cxt->args = &cache->args[0]; + } + + jspGetLambdaExpr(jsp, &expr); + + /* found == NULL is used here when executing boolean filter expressions */ + if (found) + { + res = executeItem(cxt, &expr, jb, found); + } + else + { + JsonPathBool ok = executeBoolItem(cxt, &expr, jb, false); + + if (ok == jpbUnknown) + { + cxt->args = oldargs; + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NO_JSON_ITEM), + errmsg(ERRMSG_NO_JSON_ITEM)))); /* FIXME */ + } + + res = ok == jpbTrue ? jperOk : jperNotFound; + } + + cxt->args = oldargs; + + return res; +} + +JsonPathExecResult +jspExecuteLambda(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonValueList *res, + JsonItem **params, int nparams, void **cache) +{ + return jsp->type == jpiLambda + ? recursiveExecuteLambdaExpr(cxt, jsp, jb, res, params, nparams, cache) + : recursiveExecuteLambdaVars(cxt, jsp, jb, res, params, nparams, cache); +} + /* * Implementation of several jsonpath nodes: * - jpiAny (.** accessor), @@ -2941,6 +3147,32 @@ getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, case jpiVariable: getJsonPathVariable(cxt, item, value); return; + + case jpiArgument: + { + JsonLambdaArg *arg; + char *argname; + int argnamelen; + + argname = jspGetString(item, &argnamelen); + + for (arg = cxt->args; arg; arg = arg->next) + { + if (arg->namelen == argnamelen && + !strncmp(arg->name, argname, argnamelen)) + { + *value = *arg->val; + return; /* FIXME object id */ + } + } + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("cannot find jsonpath lambda variable '%s'", + pnstrdup(argname, argnamelen)))); + break; + } + default: elog(ERROR, "unexpected jsonpath item type"); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index e4dd602532..52c36ea02f 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -43,6 +43,7 @@ static JsonPathParseItem *makeItemType(JsonPathItemType type); static JsonPathParseItem *makeItemString(JsonPathString *s); static JsonPathParseItem *makeItemVariable(JsonPathString *s); static JsonPathParseItem *makeItemKey(JsonPathString *s); +static JsonPathParseItem *makeItemArgument(JsonPathString *s); static JsonPathParseItem *makeItemNumeric(JsonPathString *s); static JsonPathParseItem *makeItemBool(bool val); static JsonPathParseItem *makeItemBinary(JsonPathItemType type, @@ -59,6 +60,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, static JsonPathParseItem *makeItemSequence(List *elems); static JsonPathParseItem *makeItemObject(List *fields); static JsonPathParseItem *makeItemCurrentN(int level); +static JsonPathParseItem *makeItemLambda(List *params, JsonPathParseItem *expr); static JsonPathParseItem *setItemOutPathMode(JsonPathParseItem *jpi); static List *setItemsOutPathMode(List *items); @@ -95,7 +97,7 @@ static List *setItemsOutPathMode(List *items); %token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P %token IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P -%token OR_P AND_P NOT_P +%token OR_P AND_P NOT_P ARROW_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P %token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P @@ -107,9 +109,10 @@ static List *setItemsOutPathMode(List *items); any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial expr_or_predicate datetime_template opt_datetime_template expr_seq expr_or_seq - object_field + object_field lambda_expr lambda_or_expr %type accessor_expr accessor_ops expr_list object_field_list + lambda_or_expr_list %type index_list @@ -160,6 +163,25 @@ expr_list: | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); } ; + +lambda_expr: + IDENT_P ARROW_P expr { $$ = makeItemLambda(list_make1( + makeItemArgument(&$1)), $3); } + | '(' expr ')' ARROW_P expr { $$ = makeItemLambda(list_make1($2), $5); } + | '(' ')' ARROW_P expr { $$ = makeItemLambda(NIL, $4); } + | '(' expr_seq ')' ARROW_P expr { $$ = makeItemLambda($2->value.sequence.elems, $5); } + ; + +lambda_or_expr: + expr + | lambda_expr + ; + +lambda_or_expr_list: + /* EMPYT*/ { $$ = NIL; } + | lambda_or_expr { $$ = list_make1($1); } + | lambda_or_expr_list ',' lambda_or_expr { $$ = lappend($1, $3); } + ; mode: STRICT_P { $$ = false; } | LAX_P { $$ = true; } @@ -174,6 +196,7 @@ scalar_value: | NUMERIC_P { $$ = makeItemNumeric(&$1); } | INT_P { $$ = makeItemNumeric(&$1); } | VARIABLE_P { $$ = makeItemVariable(&$1); } + | IDENT_P { $$ = makeItemArgument(&$1); } ; comp_op: @@ -439,6 +462,17 @@ makeItemKey(JsonPathString *s) return v; } +static JsonPathParseItem * +makeItemArgument(JsonPathString *s) +{ + JsonPathParseItem *v; + + v = makeItemString(s); + v->type = jpiArgument; + + return v; +} + static JsonPathParseItem * makeItemNumeric(JsonPathString *s) { @@ -644,6 +678,39 @@ makeItemObject(List *fields) return v; } +static JsonPathParseItem * +makeItemLambda(List *params, JsonPathParseItem *expr) +{ + JsonPathParseItem *v = makeItemType(jpiLambda); + ListCell *lc; + + v->value.lambda.params = params; + v->value.lambda.expr = expr; + + foreach(lc, params) + { + JsonPathParseItem *param1 = lfirst(lc); + ListCell *lc2; + + if (param1->type != jpiArgument || param1->next) + yyerror(NULL, "lambda arguments must be identifiers"); + + foreach(lc2, params) + { + JsonPathParseItem *param2 = lfirst(lc2); + + if (lc != lc2 && + param1->value.string.len == param2->value.string.len && + !strncmp(param1->value.string.val, + param2->value.string.val, + param1->value.string.len)) + yyerror(NULL, "lambda argument names must be unique"); + } + } + + return v; +} + static JsonPathParseItem * setItemOutPathMode(JsonPathParseItem *jpi) { diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 5b0b441c83..f249a7e051 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -182,7 +182,9 @@ hex_fail \\x{hex_dig}{0,1} \> { return GREATER_P; } -\${id} { +\=\> { return ARROW_P; } + +\$({id}|{integer}) { addstring(true, yytext + 1, yyleng - 1); addchar(false, '\0'); yylval->str = scanstring; diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 08452158e8..af3bfb524f 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -98,6 +98,8 @@ typedef enum JsonPathItemType jpiArray, /* array constructor: '[expr, ...]' */ jpiObject, /* object constructor: '{ key : value, ... }' */ jpiObjectField, /* element of object constructor: 'key : value' */ + jpiLambda, /* lambda expression: 'arg => expr' or '(arg,...) => expr' */ + jpiArgument, /* lambda argument */ jpiBinary = 0xFF /* for jsonpath operators implementation only */ } JsonPathItemType; @@ -210,6 +212,14 @@ typedef struct JsonPathItem int32 patternlen; uint32 flags; } like_regex; + + struct + { + int32 id; + int32 *params; + int32 nparams; + int32 expr; + } lambda; } content; } JsonPathItem; @@ -230,6 +240,9 @@ extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem); extern void jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val); +extern JsonPathItem *jspGetLambdaParam(JsonPathItem *func, int index, + JsonPathItem *arg); +extern JsonPathItem *jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr); extern const char *jspOperationName(JsonPathItemType type); @@ -298,6 +311,11 @@ struct JsonPathParseItem JsonPath *binary; + struct { + List *params; + JsonPathParseItem *expr; + } lambda; + /* scalars */ Numeric numeric; bool boolean; @@ -463,6 +481,14 @@ typedef int (*JsonPathVarCallback) (void *vars, bool isJsonb, char *varName, int varNameLen, JsonItem *val, JsonbValue *baseObject); +typedef struct JsonLambdaArg +{ + struct JsonLambdaArg *next; + JsonItem *val; + const char *name; + int namelen; +} JsonLambdaArg; + /* * Context of jsonpath execution. */ @@ -470,6 +496,7 @@ typedef struct JsonPathExecContext { void *vars; /* variables to substitute into jsonpath */ JsonPathVarCallback getVar; + JsonLambdaArg *args; /* for lambda evaluation */ JsonItem *root; /* for $ evaluation */ JsonItemStack stack; /* for @ evaluation */ JsonBaseObjectInfo baseObject; /* "base object" for .keyvalue() @@ -588,6 +615,11 @@ extern JsonPathExecResult jspExecuteItem(JsonPathExecContext *cxt, extern JsonPathExecResult jspExecuteItemNested(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, JsonValueList *found); +extern JsonPathExecResult jspExecuteLambda(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonItem *jb, + JsonValueList *found, + JsonItem **params, int nparams, + void **pcache); extern JsonPathBool jspCompareItems(int32 op, JsonItem *jb1, JsonItem *jb2); /* Standard error message for SQL/JSON errors */ @@ -602,5 +634,6 @@ extern JsonPathBool jspCompareItems(int32 op, JsonItem *jb1, JsonItem *jb2); #define ERRMSG_INVALID_JSON_SUBSCRIPT "invalid SQL/JSON subscript" #define ERRMSG_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION \ "invalid argument for SQL/JSON datetime function" +#define ERRMSG_NO_JSON_ITEM "no SQL/JSON item" #endif diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index 3939b4ce13..0e9f28694f 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -2068,7 +2068,7 @@ SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); -ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input +ERROR: syntax error, unexpected IDENT_P, expecting $end at or near "error" of jsonpath input -- Should fail (not supported) SELECT * FROM JSON_TABLE(json '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); ERROR: only string constants supported in JSON_TABLE path specification diff --git a/src/test/regress/expected/jsonb_sqljson.out b/src/test/regress/expected/jsonb_sqljson.out index 9fd12ca0f1..034261e2c7 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -2062,7 +2062,7 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); -- Should fail (invalid path) SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); -ERROR: syntax error, unexpected IDENT_P at or near "error" of jsonpath input +ERROR: syntax error, unexpected IDENT_P, expecting $end at or near "error" of jsonpath input -- Should fail (not supported) SELECT * FROM JSON_TABLE(jsonb '{"a": 123}', '$' || '.' || 'a' COLUMNS (foo int)); ERROR: only string constants supported in JSON_TABLE path specification From 02077ef08d0f4c3fce75eb484d170e719508d5ea Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 18 Dec 2017 18:42:22 +0300 Subject: [PATCH 64/66] Add custom jsonpath item methods and functions --- src/backend/utils/adt/jsonpath.c | 131 +++++++++++++++++++++++- src/backend/utils/adt/jsonpath_exec.c | 134 +++++++++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 72 +++++++++++-- src/backend/utils/adt/pseudotypes.c | 1 + src/include/catalog/pg_proc.dat | 9 ++ src/include/catalog/pg_type.dat | 5 + src/include/utils/jsonpath.h | 33 ++++++ src/test/regress/expected/jsonpath.out | 112 +++++++++++++++++++++ src/test/regress/sql/jsonpath.sql | 22 ++++ 9 files changed, 506 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 613adfa197..0bf79969ec 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -576,6 +576,42 @@ copyJsonPathItem(JsonPathContext *cxt, JsonPathItem *item, int level, } break; + case jpiMethod: + case jpiFunction: + { + int32 nargs = item->content.func.nargs; + int offset; + int i; + + /* assign cache id */ + appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id)); + ++cxt->id; + + appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs)); + offset = buf->len; + appendStringInfoSpaces(buf, sizeof(int32) * nargs); + + appendBinaryStringInfo(buf, (char *) &item->content.func.namelen, + sizeof(item->content.func.namelen)); + appendBinaryStringInfo(buf, item->content.func.name, + item->content.func.namelen); + appendStringInfoChar(buf, '\0'); + + for (i = 0; i < nargs; i++) + { + JsonPathItem arg; + int32 argpos; + + jspGetFunctionArg(item, i, &arg); + + argpos = copyJsonPathItem(cxt, &arg, level, NULL, NULL); + + *(int32 *) &buf->data[offset] = argpos - pos; + offset += sizeof(int32); + } + } + break; + default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -768,6 +804,39 @@ flattenJsonPathParseItem(JsonPathContext *cxt, JsonPathParseItem *item, offset += sizeof(int32); } break; + case jpiMethod: + case jpiFunction: + { + int32 nargs = list_length(item->value.func.args); + ListCell *lc; + int offset; + + /* assign cache id */ + appendBinaryStringInfo(buf, (const char *) &cxt->id, sizeof(cxt->id)); + ++cxt->id; + + appendBinaryStringInfo(buf, (char *) &nargs, sizeof(nargs)); + offset = buf->len; + appendStringInfoSpaces(buf, sizeof(int32) * nargs); + + appendBinaryStringInfo(buf, (char *) &item->value.func.namelen, + sizeof(item->value.func.namelen)); + appendBinaryStringInfo(buf, item->value.func.name, + item->value.func.namelen); + appendStringInfoChar(buf, '\0'); + + foreach(lc, item->value.func.args) + { + int32 argpos = + flattenJsonPathParseItem(cxt, lfirst(lc), + nestingLevel + 1, + insideArraySubscript); + + *(int32 *) &buf->data[offset] = argpos - pos; + offset += sizeof(int32); + } + } + break; case jpiNull: break; case jpiRoot: @@ -1278,6 +1347,31 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, if (printBracketes || jspHasNext(v)) appendStringInfoChar(buf, ')'); break; + case jpiMethod: + case jpiFunction: + if (v->type == jpiMethod) + { + jspGetMethodItem(v, &elem); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + appendStringInfoChar(buf, '.'); + } + + escape_json(buf, v->content.func.name); + appendStringInfoChar(buf, '('); + + for (i = v->type == jpiMethod ? 1 : 0; i < v->content.func.nargs; i++) + { + if (i > (v->type == jpiMethod ? 1 : 0)) + appendBinaryStringInfo(buf, ", ", 2); + + jspGetFunctionArg(v, i, &elem); + printJsonPathItem(buf, &elem, false, elem.type == jpiSequence); + } + + appendStringInfoChar(buf, ')'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -1371,11 +1465,13 @@ operationPriority(JsonPathItemType op) case jpiDiv: case jpiMod: return 4; + case jpiMethod: + return 5; case jpiPlus: case jpiMinus: - return 5; - default: return 6; + default: + return 7; } } @@ -1483,6 +1579,15 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) v->content.lambda.nparams); read_int32(v->content.lambda.expr, base, pos); break; + case jpiMethod: + case jpiFunction: + read_int32(v->content.func.id, base, pos); + read_int32(v->content.func.nargs, base, pos); + read_int32_n(v->content.func.args, base, pos, + v->content.func.nargs); + read_int32(v->content.func.namelen, base, pos); + v->content.func.name = base + pos; + break; case jpiNot: case jpiExists: case jpiIsUnknown: @@ -1581,7 +1686,9 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiArray || v->type == jpiObject || v->type == jpiLambda || - v->type == jpiArgument); + v->type == jpiArgument || + v->type == jpiFunction || + v->type == jpiMethod); if (a) jspInitByBuffer(a, v->base, v->nextPos); @@ -1717,6 +1824,24 @@ jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr) return expr; } +JsonPathItem * +jspGetFunctionArg(JsonPathItem *func, int index, JsonPathItem *arg) +{ + Assert(func->type == jpiMethod || func->type == jpiFunction); + Assert(index < func->content.func.nargs); + + jspInitByBuffer(arg, func->base, func->content.func.args[index]); + + return arg; +} + +JsonPathItem * +jspGetMethodItem(JsonPathItem *method, JsonPathItem *arg) +{ + Assert(method->type == jpiMethod); + return jspGetFunctionArg(method, 0, arg); +} + static void checkJsonPathArgsMismatch(JsonPath *jp1, JsonPath *jp2) { diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 536cc524b6..3938113841 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -66,6 +66,7 @@ #include "lib/stringinfo.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "parser/parse_func.h" #include "regex/regex.h" #include "utils/builtins.h" #include "utils/date.h" @@ -296,6 +297,9 @@ static int getJsonPathVariableFromJsonx(void *varsJsonb, bool isJsonb, static JsonPathBool executeComparison(JsonPathItem *cmp, JsonItem *lv, JsonItem *rv, void *p); static int compareNumeric(Numeric a, Numeric b); +static JsonPathExecResult executeFunction(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonItem *js, + JsonValueList *result /*, bool needBool */); static void JsonItemInitNull(JsonItem *item); static void JsonItemInitBool(JsonItem *item, bool val); @@ -839,6 +843,7 @@ executeJsonPath(JsonPath *path, void *vars, JsonPathVarCallback getVar, cxt.vars = vars; cxt.getVar = getVar; + cxt.args = NULL; cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = &jsi; @@ -1912,6 +1917,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, elog(ERROR, "jsonpath lambda expression cannot be executed directly"); break; + case jpiMethod: + case jpiFunction: + res = executeFunction(cxt, jsp, jb, found); + break; + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } @@ -2052,6 +2062,130 @@ executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, return executeItem(cxt, jsp, jb, found); } +typedef struct JsonPathFuncCache +{ + FmgrInfo finfo; + JsonPathItem *args; + void **argscache; +} JsonPathFuncCache; + +static JsonPathFuncCache * +prepareFunctionCache(JsonPathExecContext *cxt, JsonPathItem *jsp) +{ + MemoryContext oldcontext; + JsonPathFuncCache *cache = cxt->cache[jsp->content.func.id]; + List *funcname = list_make1(makeString(jsp->content.func.name)); + Oid argtypes[] = {JSONPATH_FCXTOID}; + Oid funcid; + int32 i; + + if (cache) + return cache; + + funcid = LookupFuncName(funcname, + sizeof(argtypes) / sizeof(argtypes[0]), argtypes, + false); + + if (get_func_rettype(funcid) != INT8OID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type of jsonpath item function %s is not %s", + NameListToString(funcname), format_type_be(INT8OID)))); + + oldcontext = MemoryContextSwitchTo(cxt->cache_mcxt); + + cache = cxt->cache[jsp->content.func.id] = palloc0(sizeof(*cache)); + + fmgr_info(funcid, &cache->finfo); + + cache->args = palloc(sizeof(*cache->args) * jsp->content.func.nargs); + cache->argscache = palloc0(sizeof(*cache->argscache) * + jsp->content.func.nargs); + + for (i = 0; i < jsp->content.func.nargs; i++) + jspGetFunctionArg(jsp, i, &cache->args[i]); + + MemoryContextSwitchTo(oldcontext); + + return cache; +} + +static JsonPathExecResult +executeFunction(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonItem *jb, + JsonValueList *result /*, bool needBool */) +{ + JsonPathFuncCache *cache = prepareFunctionCache(cxt, jsp); + JsonPathFuncContext fcxt; + JsonValueList tmpres = {0}; + JsonValueListIterator tmpiter; + JsonPathExecResult res; + JsonItem *jsi; + + fcxt.cxt = cxt; + fcxt.funcname = jsp->content.func.name; + fcxt.jb = jb; + fcxt.result = jspHasNext(jsp) ? &tmpres : result; + fcxt.args = cache->args; + fcxt.argscache = cache->argscache; + fcxt.nargs = jsp->content.func.nargs; + + if (jsp->type == jpiMethod) + { + JsonValueList items = {0}; + JsonValueListIterator iter; + + /* skip first item argument */ + fcxt.args++; + fcxt.argscache++; + fcxt.nargs--; + + res = executeItem(cxt, &cache->args[0], jb, &items); + + if (jperIsError(res)) + return res; + + JsonValueListInitIterator(&items, &iter); + + while ((jsi = JsonValueListNext(&items, &iter))) + { + fcxt.item = jsi; + + res = (JsonPathExecResult) + DatumGetPointer(FunctionCall2(&cache->finfo, + PointerGetDatum(&fcxt), + PointerGetDatum(NULL))); + if (jperIsError(res)) + return res; + } + } + else + { + fcxt.item = NULL; + + res = (JsonPathExecResult) + DatumGetPointer(FunctionCall2(&cache->finfo, + PointerGetDatum(&fcxt), + PointerGetDatum(NULL))); + if (jperIsError(res)) + return res; + } + + if (!jspHasNext(jsp)) + return res; + + JsonValueListInitIterator(&tmpres, &tmpiter); + + while ((jsi = JsonValueListNext(&tmpres, &tmpiter))) + { + res = executeNextItem(cxt, jsp, NULL, jsi, result, false /*, needBool FIXME */); + + if (jperIsError(res)) + return res; + } + + return res; +} + /* * Same as executeItemOptUnwrapResult(), but with error suppression. */ diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 52c36ea02f..add754e6eb 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -51,6 +51,8 @@ static JsonPathParseItem *makeItemBinary(JsonPathItemType type, JsonPathParseItem *ra); static JsonPathParseItem *makeItemUnary(JsonPathItemType type, JsonPathParseItem *a); +static JsonPathParseItem *makeItemFunc(JsonPathString *name, List *args, + bool method); static JsonPathParseItem *makeItemList(List *list); static JsonPathParseItem *makeIndexArray(List *list); static JsonPathParseItem *makeAny(int first, int last); @@ -120,7 +122,7 @@ static List *setItemsOutPathMode(List *items); %type mode -%type key_name +%type key_name method_name builtin_method_name function_name %type any_level @@ -243,6 +245,8 @@ path_primary: | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); } | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); } | '{' object_field_list '}' { $$ = makeItemObject($2); } + | function_name '(' lambda_or_expr_list ')' + { $$ = makeItemFunc(&$1, $3, false); } ; object_field_list: @@ -334,6 +338,8 @@ accessor_op: | '.' DATETIME_P '(' datetime_template ',' expr ')' { $$ = makeItemBinary(jpiDatetime, $4, $6); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } + | '.' method_name '(' lambda_or_expr_list ')' + { $$ = makeItemFunc(&$2, $4, true); } ; datetime_template: @@ -349,7 +355,24 @@ key: key_name { $$ = makeItemKey(&$1); } ; -key_name: +function_name: + IDENT_P + | STRING_P +/* | TO_P */ + | NULL_P + | TRUE_P + | FALSE_P + | IS_P + | UNKNOWN_P + | LAST_P + | STARTS_P + | WITH_P + | LIKE_REGEX_P + | FLAG_P + | builtin_method_name + ; + +method_name: IDENT_P | STRING_P | TO_P @@ -361,7 +384,15 @@ key_name: | EXISTS_P | STRICT_P | LAX_P - | ABS_P + | LAST_P + | STARTS_P + | WITH_P + | LIKE_REGEX_P + | FLAG_P + ; + +builtin_method_name: + ABS_P | SIZE_P | TYPE_P | FLOOR_P @@ -369,11 +400,11 @@ key_name: | CEILING_P | DATETIME_P | KEYVALUE_P - | LAST_P - | STARTS_P - | WITH_P - | LIKE_REGEX_P - | FLAG_P + ; + +key_name: + method_name + | builtin_method_name ; method: @@ -533,6 +564,18 @@ makeItemUnary(JsonPathItemType type, JsonPathParseItem *a) return v; } +static JsonPathParseItem * +makeItemFunc(JsonPathString *name, List *args, bool method) +{ + JsonPathParseItem *v = makeItemType(method ? jpiMethod : jpiFunction); + + v->value.func.name = name->val; + v->value.func.namelen = name->len; + v->value.func.args = args; + + return v; +} + static JsonPathParseItem * makeItemList(List *list) { @@ -553,8 +596,17 @@ makeItemList(List *list) { JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell); - end->next = c; - end = c; + if (c->type == jpiMethod) + { + c->value.func.args = lcons(head, c->value.func.args); + head = c; + end = c; + } + else + { + end->next = c; + end = c; + } } return head; diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index 5c886cfe96..184b918055 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -419,3 +419,4 @@ PSEUDOTYPE_DUMMY_IO_FUNCS(opaque); PSEUDOTYPE_DUMMY_IO_FUNCS(anyelement); PSEUDOTYPE_DUMMY_IO_FUNCS(anynonarray); PSEUDOTYPE_DUMMY_IO_FUNCS(table_am_handler); +PSEUDOTYPE_DUMMY_IO_FUNCS(jsonpath_fcxt); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 29537a3860..c0d98cdf26 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9466,6 +9466,15 @@ proname => 'jsonpath_bind_jsonb', prorettype => 'jsonpath', proargtypes => 'jsonpath jsonb', prosrc => 'jsonpath_bind_jsonb' }, +# jsonpath_fcxt +{ oid => '6122', descr => 'I/O', + proname => 'jsonpath_fcxt_in', proisstrict => 'f', + prorettype => 'jsonpath_fcxt', proargtypes => 'cstring', + prosrc => 'jsonpath_fcxt_in' }, +{ oid => '6123', descr => 'I/O', + proname => 'jsonpath_fcxt_out', prorettype => 'cstring', + proargtypes => 'jsonpath_fcxt', prosrc => 'jsonpath_fcxt_out' }, + # txid { oid => '2939', descr => 'I/O', proname => 'txid_snapshot_in', prorettype => 'txid_snapshot', diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat index be49e00114..9dc14deb7f 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -594,5 +594,10 @@ typname => 'anyrange', typlen => '-1', typbyval => 'f', typtype => 'p', typcategory => 'P', typinput => 'anyrange_in', typoutput => 'anyrange_out', typreceive => '-', typsend => '-', typalign => 'd', typstorage => 'x' }, +{ oid => '6103', + typname => 'jsonpath_fcxt', typlen => 'SIZEOF_POINTER', typbyval => 't', + typtype => 'p', typcategory => 'P', typinput => 'jsonpath_fcxt_in', + typoutput => 'jsonpath_fcxt_out', typreceive => '-', typsend => '-', + typalign => 'ALIGNOF_POINTER' }, ] diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index af3bfb524f..5b3cdbc091 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -100,6 +100,8 @@ typedef enum JsonPathItemType jpiObjectField, /* element of object constructor: 'key : value' */ jpiLambda, /* lambda expression: 'arg => expr' or '(arg,...) => expr' */ jpiArgument, /* lambda argument */ + jpiMethod, /* user item method */ + jpiFunction, /* user function */ jpiBinary = 0xFF /* for jsonpath operators implementation only */ } JsonPathItemType; @@ -220,6 +222,16 @@ typedef struct JsonPathItem int32 nparams; int32 expr; } lambda; + + struct + { + int32 id; + int32 item; + char *name; + int32 namelen; + int32 *args; + int32 nargs; + } func; } content; } JsonPathItem; @@ -243,6 +255,9 @@ extern void jspGetObjectField(JsonPathItem *v, int i, extern JsonPathItem *jspGetLambdaParam(JsonPathItem *func, int index, JsonPathItem *arg); extern JsonPathItem *jspGetLambdaExpr(JsonPathItem *lambda, JsonPathItem *expr); +extern JsonPathItem *jspGetFunctionArg(JsonPathItem *func, int index, + JsonPathItem *arg); +extern JsonPathItem *jspGetMethodItem(JsonPathItem *method, JsonPathItem *arg); extern const char *jspOperationName(JsonPathItemType type); @@ -316,6 +331,12 @@ struct JsonPathParseItem JsonPathParseItem *expr; } lambda; + struct { + List *args; + char *name; + int32 namelen; + } func; + /* scalars */ Numeric numeric; bool boolean; @@ -557,6 +578,18 @@ typedef struct JsonValueListIterator JsonItem *next; } JsonValueListIterator; +typedef struct JsonPathFuncContext +{ + JsonPathExecContext *cxt; + JsonValueList *result; + const char *funcname; + JsonItem *jb; + JsonItem *item; + JsonPathItem *args; + void **argscache; + int nargs; +} JsonPathFuncContext; + extern JsonItem *JsonbValueToJsonItem(JsonbValue *jbv, JsonItem *jsi); extern Jsonb *JsonItemToJsonb(JsonItem *jsi); diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 387e7feeb0..851d2f52cc 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -1309,3 +1309,115 @@ select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ json $[*]?(@ > -1.23 && @ <= 5.0) (1 row) +-- extension: user-defined item methods and functions with lambda expressions +select jsonpath 'foo()'; + jsonpath +---------- + "foo"() +(1 row) + +select jsonpath '$.foo()'; + jsonpath +----------- + $."foo"() +(1 row) + +select jsonpath 'foo($[*])'; + jsonpath +------------- + "foo"($[*]) +(1 row) + +select jsonpath '$.foo("bar")'; + jsonpath +---------------- + $."foo"("bar") +(1 row) + +select jsonpath 'foo($[*], "bar")'; + jsonpath +-------------------- + "foo"($[*], "bar") +(1 row) + +select jsonpath '$.foo("bar", 123 + 456, "baz".type())'; + jsonpath +----------------------------------------- + $."foo"("bar", 123 + 456, "baz".type()) +(1 row) + +select jsonpath 'foo($[*], "bar", 123 + 456, "baz".type())'; + jsonpath +--------------------------------------------- + "foo"($[*], "bar", 123 + 456, "baz".type()) +(1 row) + +select jsonpath '$.foo(() => 1)'; + jsonpath +------------------ + $."foo"(() => 1) +(1 row) + +select jsonpath 'foo($[*], () => 1)'; + jsonpath +---------------------- + "foo"($[*], () => 1) +(1 row) + +select jsonpath '$.foo((x) => x + 5)'; + jsonpath +----------------------- + $."foo"((x) => x + 5) +(1 row) + +select jsonpath 'foo($[*], (x) => x + 5)'; + jsonpath +--------------------------- + "foo"($[*], (x) => x + 5) +(1 row) + +select jsonpath '$.foo(x => x + 5)'; + jsonpath +----------------------- + $."foo"((x) => x + 5) +(1 row) + +select jsonpath 'foo($[*], x => x + 5)'; + jsonpath +--------------------------- + "foo"($[*], (x) => x + 5) +(1 row) + +select jsonpath '$.foo((x, y) => x + 5 * y)'; + jsonpath +------------------------------ + $."foo"((x, y) => x + 5 * y) +(1 row) + +select jsonpath 'foo($[*], (x, y) => x + 5 * y)'; + jsonpath +---------------------------------- + "foo"($[*], (x, y) => x + 5 * y) +(1 row) + +select jsonpath '$.foo((x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()'; + jsonpath +------------------------------------------------------------------------------------ + $."foo"((x, y) => x + 5 * y, (z) => z.type(), () => $."bar"((x) => x + 1))."baz"() +(1 row) + +select jsonpath 'foo($[*], (x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()'; + jsonpath +---------------------------------------------------------------------------------------- + "foo"($[*], (x, y) => x + 5 * y, (z) => z.type(), () => $."bar"((x) => x + 1))."baz"() +(1 row) + +-- should fail +select jsonpath '$.foo((x, y.a) => x + 5)'; +ERROR: lambda arguments must be identifiers at or near ")" of jsonpath input +LINE 1: select jsonpath '$.foo((x, y.a) => x + 5)'; + ^ +select jsonpath '$.foo((x, y + 1, z) => x + y)'; +ERROR: lambda arguments must be identifiers at or near ")" of jsonpath input +LINE 1: select jsonpath '$.foo((x, y + 1, z) => x + y)'; + ^ diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index bafc1375fc..16818f40ca 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -254,3 +254,25 @@ select jsonpath '$.a + $a.double()' @ jsonb '{"a": "abc"}'; select jsonpath '$.a + $a.x.double()' @ jsonb '{"a": {"x": -12.34}}'; select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23, "max": 5.0}'; select jsonpath '$[*] ? (@ > $min && @ <= $max)' @ jsonb '{"min": -1.23}' @ jsonb '{"max": 5.0}'; + +-- extension: user-defined item methods and functions with lambda expressions +select jsonpath 'foo()'; +select jsonpath '$.foo()'; +select jsonpath 'foo($[*])'; +select jsonpath '$.foo("bar")'; +select jsonpath 'foo($[*], "bar")'; +select jsonpath '$.foo("bar", 123 + 456, "baz".type())'; +select jsonpath 'foo($[*], "bar", 123 + 456, "baz".type())'; +select jsonpath '$.foo(() => 1)'; +select jsonpath 'foo($[*], () => 1)'; +select jsonpath '$.foo((x) => x + 5)'; +select jsonpath 'foo($[*], (x) => x + 5)'; +select jsonpath '$.foo(x => x + 5)'; +select jsonpath 'foo($[*], x => x + 5)'; +select jsonpath '$.foo((x, y) => x + 5 * y)'; +select jsonpath 'foo($[*], (x, y) => x + 5 * y)'; +select jsonpath '$.foo((x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()'; +select jsonpath 'foo($[*], (x, y) => x + 5 * y, z => z.type(), () => $.bar(x => x + 1)).baz()'; +-- should fail +select jsonpath '$.foo((x, y.a) => x + 5)'; +select jsonpath '$.foo((x, y + 1, z) => x + y)'; From feb6aa81b656e10a9283941e514ac0bc74f0c10c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 27 Dec 2017 01:16:59 +0300 Subject: [PATCH 65/66] Add tests for custom jsonpath item methods and functions --- src/test/regress/expected/jsonb_jsonpath.out | 62 ++++++ .../regress/input/create_function_1.source | 6 + .../regress/output/create_function_1.source | 5 + src/test/regress/regress.c | 199 +++++++++++++++++- src/test/regress/sql/jsonb_jsonpath.sql | 15 ++ 5 files changed, 286 insertions(+), 1 deletion(-) diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 997b6c1528..6c7c3ffa44 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -2695,3 +2695,65 @@ select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 {"a": [{"b": 1}, {"b": 2}]} (1 row) +-- extension: user-defined functions and item methods +-- array_map(jsonpath_fcxt, jsonb) function created in create_function_1.sql +-- array_map() item method +select jsonb_path_query('1', 'strict $.array_map(x => x + 10)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .array_map() is applied to not an array +select jsonb_path_query('1', 'lax $.array_map(x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)'); + jsonb_path_query +------------------ + [11, 12, 13] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)[*]'); + jsonb_path_query +------------------ + 11 + 12 + 13 +(3 rows) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.array_map(a => a.array_map(x => x + 10))'); + jsonb_path_query +---------------------------------------- + [[11, 12], [13, 14, 15], [], [16, 17]] +(1 row) + +-- array_map() function +select jsonb_path_query('1', 'strict array_map($, x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('1', 'lax array_map($, x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[3, 4, 5]', 'array_map($[*], (x, i) => x + i * 10)'); + jsonb_path_query +------------------ + 3 + 14 + 25 +(3 rows) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'array_map($[*], x => [array_map(x[*], x => x + 10)])'); + jsonb_path_query +------------------ + [11, 12] + [13, 14, 15] + [] + [16, 17] +(4 rows) + diff --git a/src/test/regress/input/create_function_1.source b/src/test/regress/input/create_function_1.source index 223454a5ea..0eb4383196 100644 --- a/src/test/regress/input/create_function_1.source +++ b/src/test/regress/input/create_function_1.source @@ -73,6 +73,12 @@ CREATE FUNCTION test_support_func(internal) AS '@libdir@/regress@DLSUFFIX@', 'test_support_func' LANGUAGE C STRICT; +-- Tests creating a custom jsonpath item method +CREATE FUNCTION array_map(jsonpath_fcxt) + RETURNS int8 + AS '@libdir@/regress@DLSUFFIX@', 'jsonpath_array_map' + LANGUAGE C; + -- Things that shouldn't work: CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL diff --git a/src/test/regress/output/create_function_1.source b/src/test/regress/output/create_function_1.source index 5f43e8de81..511257e1ee 100644 --- a/src/test/regress/output/create_function_1.source +++ b/src/test/regress/output/create_function_1.source @@ -64,6 +64,11 @@ CREATE FUNCTION test_support_func(internal) RETURNS internal AS '@libdir@/regress@DLSUFFIX@', 'test_support_func' LANGUAGE C STRICT; +-- Tests creating a custom jsonpath item method +CREATE FUNCTION array_map(jsonpath_fcxt) + RETURNS int8 + AS '@libdir@/regress@DLSUFFIX@', 'jsonpath_array_map' + LANGUAGE C; -- Things that shouldn't work: CREATE FUNCTION test1 (int) RETURNS int LANGUAGE SQL AS 'SELECT ''not an integer'';'; diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c index 7f03b7e857..aa202f375d 100644 --- a/src/test/regress/regress.c +++ b/src/test/regress/regress.c @@ -35,7 +35,8 @@ #include "optimizer/plancat.h" #include "port/atomics.h" #include "utils/builtins.h" -#include "utils/geo_decls.h" +#include "utils/geo_decls.h" +#include "utils/jsonpath.h" #include "utils/rel.h" #include "utils/typcache.h" #include "utils/memutils.h" @@ -940,3 +941,199 @@ test_support_func(PG_FUNCTION_ARGS) PG_RETURN_POINTER(ret); } + +PG_FUNCTION_INFO_V1(jsonpath_array_map); +Datum +jsonpath_array_map(PG_FUNCTION_ARGS) +{ + JsonPathFuncContext *fcxt = (JsonPathFuncContext *) PG_GETARG_POINTER(0); + JsonPathExecContext *cxt = fcxt->cxt; + JsonItem *jb = fcxt->item; + JsonPathItem *func = &fcxt->args[jb ? 0 : 1]; + void **funccache = &fcxt->argscache[jb ? 0 : 1]; + JsonPathExecResult res; + JsonItem *args[3]; + JsonItem jbvidx; + int index = 0; + int nargs = 1; + + if (fcxt->nargs != (jb ? 1 : 2)) + { + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg(ERRMSG_JSON_SCALAR_REQUIRED), + errdetail("jsonpath .array_map() requires %d arguments " + "but given %d", jb ? 1 : 2, fcxt->nargs))); + + PG_RETURN_INT64(jperError); + } + + if (func->type == jpiLambda && func->content.lambda.nparams > 1) + { + args[nargs++] = &jbvidx; + JsonItemGetType(&jbvidx) = jbvNumeric; + } + + if (!jb) + { + JsonValueList items = {0}; + JsonValueListIterator iter; + JsonItem *item; + + res = jspExecuteItem(cxt, &fcxt->args[0], fcxt->jb, &items); + + if (jperIsError(res)) + PG_RETURN_INT64(res); + + JsonValueListInitIterator(&items, &iter); + + while ((item = JsonValueListNext(&items, &iter))) + { + JsonValueList reslist = {0}; + + args[0] = item; + + if (nargs > 1) + { + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(index))); + index++; + } + + res = jspExecuteLambda(cxt, func, fcxt->jb, &reslist, + args, nargs, funccache); + + if (jperIsError(res)) + PG_RETURN_INT64(res); + + if (JsonValueListLength(&reslist) != 1) + { + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("lambda expression in .array_map() " + "should return singleton item"))); + + PG_RETURN_INT64(jperError); + } + + JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist)); + } + } + else if (JsonbType(jb) != jbvArray) + { + JsonValueList reslist = {0}; + + if (!jspAutoWrap(cxt)) + { + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND), + errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND), + errdetail("jsonpath .array_map() is applied to " + "not an array"))); + + PG_RETURN_INT64(jperError); + } + + args[0] = jb; + + if (nargs > 1) + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(0))); + + res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache); + + if (jperIsError(res)) + PG_RETURN_INT64(res); + + if (JsonValueListLength(&reslist) != 1) + { + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("lambda expression in jsonpath .array_map() " + "should return singleton item"))); + + PG_RETURN_INT64(jperError); + } + + JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist)); + } + else + { + JsonbValue elembuf; + JsonbValue *elem; + JsonxIterator it; + JsonbIteratorToken tok; + JsonValueList result = {0}; + int size = JsonxArraySize(jb, cxt->isJsonb); + int i; + bool isBinary = JsonItemIsBinary(jb); + + if (isBinary && size > 0) + { + elem = &elembuf; + JsonxIteratorInit(&it, JsonItemBinary(jb).data, cxt->isJsonb); + tok = JsonxIteratorNext(&it, &elembuf, false); + if (tok != WJB_BEGIN_ARRAY) + elog(ERROR, "unexpected jsonb token at the array start"); + } + + if (nargs > 1) + { + nargs = 3; + args[2] = jb; + } + + for (i = 0; i < size; i++) + { + JsonValueList reslist = {0}; + JsonItem elemjsi; + + if (isBinary) + { + tok = JsonxIteratorNext(&it, elem, true); + if (tok != WJB_ELEM) + break; + } + else + elem = &JsonItemArray(jb).elems[i]; + + args[0] = JsonbValueToJsonItem(elem, &elemjsi); + + if (nargs > 1) + { + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(index))); + index++; + } + + res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache); + + if (jperIsError(res)) + PG_RETURN_INT64(res); + + if (JsonValueListLength(&reslist) != 1) + { + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("lambda expression in jsonpath .array_map() " + "should return singleton item"))); + + PG_RETURN_INT64(jperError); + } + + JsonValueListConcat(&result, reslist); + } + + JsonAppendWrappedItems(fcxt->result, &result, cxt->isJsonb); + } + + PG_RETURN_INT64(jperOk); +} diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 4374191a11..ea6aea5c9c 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -607,3 +607,18 @@ select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[ select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.a[*].(b)'); select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a)[*].(b)'); select jsonb_path_query('{"a": [{"b": 1, "c": 10}, {"b": 2, "c": 20}]}', '$.(a[0 to 1].b)'); + +-- extension: user-defined functions and item methods +-- array_map(jsonpath_fcxt, jsonb) function created in create_function_1.sql +-- array_map() item method +select jsonb_path_query('1', 'strict $.array_map(x => x + 10)'); +select jsonb_path_query('1', 'lax $.array_map(x => x + 10)'); +select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)'); +select jsonb_path_query('[1, 2, 3]', '$.array_map(x => x + 10)[*]'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.array_map(a => a.array_map(x => x + 10))'); + +-- array_map() function +select jsonb_path_query('1', 'strict array_map($, x => x + 10)'); +select jsonb_path_query('1', 'lax array_map($, x => x + 10)'); +select jsonb_path_query('[3, 4, 5]', 'array_map($[*], (x, i) => x + i * 10)'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'array_map($[*], x => [array_map(x[*], x => x + 10)])'); From efea30ec9eafc31428b7c04337c19ecc8c293b97 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 18 Dec 2017 18:43:59 +0300 Subject: [PATCH 66/66] Add contrib/jsonpathx --- contrib/jsonpathx/Makefile | 22 + contrib/jsonpathx/expected/jsonpathx_json.out | 439 +++++++++ .../jsonpathx/expected/jsonpathx_jsonb.out | 439 +++++++++ contrib/jsonpathx/jsonpathx--1.0.sql | 44 + contrib/jsonpathx/jsonpathx.c | 842 ++++++++++++++++++ contrib/jsonpathx/jsonpathx.control | 5 + contrib/jsonpathx/sql/jsonpathx_json.sql | 89 ++ contrib/jsonpathx/sql/jsonpathx_jsonb.sql | 89 ++ 8 files changed, 1969 insertions(+) create mode 100644 contrib/jsonpathx/Makefile create mode 100644 contrib/jsonpathx/expected/jsonpathx_json.out create mode 100644 contrib/jsonpathx/expected/jsonpathx_jsonb.out create mode 100644 contrib/jsonpathx/jsonpathx--1.0.sql create mode 100644 contrib/jsonpathx/jsonpathx.c create mode 100644 contrib/jsonpathx/jsonpathx.control create mode 100644 contrib/jsonpathx/sql/jsonpathx_json.sql create mode 100644 contrib/jsonpathx/sql/jsonpathx_jsonb.sql diff --git a/contrib/jsonpathx/Makefile b/contrib/jsonpathx/Makefile new file mode 100644 index 0000000000..5ba50b8b25 --- /dev/null +++ b/contrib/jsonpathx/Makefile @@ -0,0 +1,22 @@ +# contrib/jsonpathx/Makefile + +MODULE_big = jsonpathx +OBJS = jsonpathx.o + +EXTENSION = jsonpathx +DATA = jsonpathx--1.0.sql +PGFILEDESC = "jsonpathx - extended JSON path item methods" + +REGRESS = jsonpathx_jsonb jsonpathx_json + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/jsonpathx +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + diff --git a/contrib/jsonpathx/expected/jsonpathx_json.out b/contrib/jsonpathx/expected/jsonpathx_json.out new file mode 100644 index 0000000000..c7e9d4ea61 --- /dev/null +++ b/contrib/jsonpathx/expected/jsonpathx_json.out @@ -0,0 +1,439 @@ +CREATE EXTENSION jsonpathx; +-- map item method +select json_path_query('1', 'strict $.map(x => x + 10)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .map() is applied to not an array +select json_path_query('1', 'lax $.map(x => x + 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('[1, 2, 3]', '$.map(x => x + 10)'); + json_path_query +----------------- + [11, 12, 13] +(1 row) + +select json_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]'); + json_path_query +----------------- + 11 + 12 + 13 +(3 rows) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))'); + json_path_query +---------------------------------------- + [[11, 12], [13, 14, 15], [], [16, 17]] +(1 row) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))'); + json_path_query +------------------------------ + [11, 12, 13, 14, 15, 16, 17] +(1 row) + +-- map function +select json_path_query('1', 'strict map($, x => x + 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('1', 'lax map($, x => x + 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('[1, 2, 3]', 'map($[*], x => x + 10)'); + json_path_query +----------------- + 11 + 12 + 13 +(3 rows) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])'); + json_path_query +----------------- + [11, 12] + [13, 14, 15] + [] + [16, 17] +(4 rows) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))'); + json_path_query +----------------- + 11 + 12 + 13 + 14 + 15 + 16 + 17 +(7 rows) + +-- reduce/fold item methods +select json_path_query('1', 'strict $.reduce((x, y) => x + y)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .reduce() is applied to not an array +select json_path_query('1', 'lax $.reduce((x, y) => x + y)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('1', 'strict $.fold((x, y) => x + y, 10)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .fold() is applied to not an array +select json_path_query('1', 'lax $.fold((x, y) => x + y, 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)'); + json_path_query +----------------- + 6 +(1 row) + +select json_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)'); + json_path_query +----------------- + 106 +(1 row) + +select json_path_query('[]', '$.reduce((x, y) => x + y)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', '$.fold((x, y) => x + y, 100)'); + json_path_query +----------------- + 100 +(1 row) + +select json_path_query('[1]', '$.reduce((x, y) => x + y)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])'); + json_path_query +------------------- + [[[[], 1], 2], 3] +(1 row) + +select json_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])'); + json_path_query +------------------- + [[[[], 3], 2], 1] +(1 row) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + json_path_query +----------------- + 1428 +(1 row) + +-- reduce/fold functions +select json_path_query('1', 'strict reduce($, (x, y) => x + y)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('1', 'lax reduce($, (x, y) => x + y)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('1', 'strict fold($, (x, y) => x + y, 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('1', 'lax fold($, (x, y) => x + y, 10)'); + json_path_query +----------------- + 11 +(1 row) + +select json_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)'); + json_path_query +----------------- + 6 +(1 row) + +select json_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)'); + json_path_query +----------------- + 106 +(1 row) + +select json_path_query('[]', 'reduce($[*], (x, y) => x + y)'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'fold($[*], (x, y) => x + y, 100)'); + json_path_query +----------------- + 100 +(1 row) + +select json_path_query('[1]', 'reduce($[*], (x, y) => x + y)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])'); + json_path_query +------------------- + [[[[], 1], 2], 3] +(1 row) + +select json_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])'); + json_path_query +------------------- + [[[[], 3], 2], 1] +(1 row) + +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + json_path_query +----------------- + 1428 +(1 row) + +-- min/max item methods +select json_path_query('1', 'strict $.min()'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .min() is applied to not an array +select json_path_query('1', 'lax $.min()'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[]', '$.min()'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', '$.max()'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[null]', '$.min()'); + json_path_query +----------------- + null +(1 row) + +select json_path_query('[null]', '$.max()'); + json_path_query +----------------- + null +(1 row) + +select json_path_query('[1, 2, 3]', '$.min()'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1, 2, 3]', '$.max()'); + json_path_query +----------------- + 3 +(1 row) + +select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()'); + json_path_query +----------------- + 5 +(1 row) + +select json_path_query('["aa", null, "a", "bbb"]', '$.min()'); + json_path_query +----------------- + "a" +(1 row) + +select json_path_query('["aa", null, "a", "bbb"]', '$.max()'); + json_path_query +----------------- + "bbb" +(1 row) + +select json_path_query('[1, null, "2"]', '$.max()'); +ERROR: SQL/JSON scalar required +DETAIL: boolean lambda expression in jsonpath .max() returned Unknown +-- min/max functions +select json_path_query('1', 'strict min($)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('1', 'lax min($)'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[]', 'min($[*])'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[]', 'max($[*])'); + json_path_query +----------------- +(0 rows) + +select json_path_query('[null]', 'min($[*])'); + json_path_query +----------------- + null +(1 row) + +select json_path_query('[null]', 'max($[*])'); + json_path_query +----------------- + null +(1 row) + +select json_path_query('[1, 2, 3]', 'min($[*])'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[1, 2, 3]', 'max($[*])'); + json_path_query +----------------- + 3 +(1 row) + +select json_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])'); + json_path_query +----------------- + 1 +(1 row) + +select json_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])'); + json_path_query +----------------- + 5 +(1 row) + +select json_path_query('["aa", null, "a", "bbb"]', 'min($[*])'); + json_path_query +----------------- + "a" +(1 row) + +select json_path_query('["aa", null, "a", "bbb"]', 'max($[*])'); + json_path_query +----------------- + "bbb" +(1 row) + +select json_path_query('[1, null, "2"]', 'max($[*])'); +ERROR: SQL/JSON scalar required +DETAIL: boolean lambda expression in jsonpath .max() returned Unknown +-- tests for simplified variable-based lambda syntax +select json_path_query('[1, 2, 3]', '$.map($1 + 100)'); + json_path_query +----------------- + [101, 102, 103] +(1 row) + +select json_path_query('[1, 2, 3]', 'map($[*], $1 + 100)'); + json_path_query +----------------- + 101 + 102 + 103 +(3 rows) + +select json_path_query('[1, 2, 3]', '$.reduce($1 + $2)'); + json_path_query +----------------- + 6 +(1 row) + +select json_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)'); + json_path_query +----------------- + 6 +(1 row) + +select json_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)'); + json_path_query +----------------- + 106 +(1 row) + +select json_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)'); + json_path_query +----------------- + 106 +(1 row) + +-- more complex tests +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]'); + json_path_query +--------------------- + {"n": 0, "sum": 0} + {"n": 1, "sum": 1} + {"n": 2, "sum": 3} + {"n": 3, "sum": 6} + {"n": 4, "sum": 10} + {"n": 5, "sum": 15} + {"n": 6, "sum": 21} + {"n": 7, "sum": 28} + {"n": 8, "sum": 36} + {"n": 9, "sum": 45} +(10 rows) + +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]'); + json_path_query +------------------- + {"n": 0, "s": 0} + {"n": 1, "s": 1} + {"n": 2, "s": 3} + {"n": 3, "s": 6} + {"n": 4, "s": 10} + {"n": 5, "s": 15} + {"n": 6, "s": 21} + {"n": 7, "s": 28} + {"n": 8, "s": 36} + {"n": 9, "s": 45} +(10 rows) + +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)'); + json_path_query +------------------- + 61218243036424854 +(1 row) + +DROP EXTENSION jsonpathx; diff --git a/contrib/jsonpathx/expected/jsonpathx_jsonb.out b/contrib/jsonpathx/expected/jsonpathx_jsonb.out new file mode 100644 index 0000000000..44aa15f3cb --- /dev/null +++ b/contrib/jsonpathx/expected/jsonpathx_jsonb.out @@ -0,0 +1,439 @@ +CREATE EXTENSION jsonpathx; +-- map item method +select jsonb_path_query('1', 'strict $.map(x => x + 10)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .map() is applied to not an array +select jsonb_path_query('1', 'lax $.map(x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)'); + jsonb_path_query +------------------ + [11, 12, 13] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]'); + jsonb_path_query +------------------ + 11 + 12 + 13 +(3 rows) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))'); + jsonb_path_query +---------------------------------------- + [[11, 12], [13, 14, 15], [], [16, 17]] +(1 row) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))'); + jsonb_path_query +------------------------------ + [11, 12, 13, 14, 15, 16, 17] +(1 row) + +-- map function +select jsonb_path_query('1', 'strict map($, x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('1', 'lax map($, x => x + 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'map($[*], x => x + 10)'); + jsonb_path_query +------------------ + 11 + 12 + 13 +(3 rows) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])'); + jsonb_path_query +------------------ + [11, 12] + [13, 14, 15] + [] + [16, 17] +(4 rows) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))'); + jsonb_path_query +------------------ + 11 + 12 + 13 + 14 + 15 + 16 + 17 +(7 rows) + +-- reduce/fold item methods +select jsonb_path_query('1', 'strict $.reduce((x, y) => x + y)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .reduce() is applied to not an array +select jsonb_path_query('1', 'lax $.reduce((x, y) => x + y)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('1', 'strict $.fold((x, y) => x + y, 10)'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .fold() is applied to not an array +select jsonb_path_query('1', 'lax $.fold((x, y) => x + y, 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)'); + jsonb_path_query +------------------ + 6 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)'); + jsonb_path_query +------------------ + 106 +(1 row) + +select jsonb_path_query('[]', '$.reduce((x, y) => x + y)'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[]', '$.fold((x, y) => x + y, 100)'); + jsonb_path_query +------------------ + 100 +(1 row) + +select jsonb_path_query('[1]', '$.reduce((x, y) => x + y)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])'); + jsonb_path_query +------------------- + [[[[], 1], 2], 3] +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])'); + jsonb_path_query +------------------- + [[[[], 3], 2], 1] +(1 row) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + jsonb_path_query +------------------ + 1428 +(1 row) + +-- reduce/fold functions +select jsonb_path_query('1', 'strict reduce($, (x, y) => x + y)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('1', 'lax reduce($, (x, y) => x + y)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('1', 'strict fold($, (x, y) => x + y, 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('1', 'lax fold($, (x, y) => x + y, 10)'); + jsonb_path_query +------------------ + 11 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)'); + jsonb_path_query +------------------ + 6 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)'); + jsonb_path_query +------------------ + 106 +(1 row) + +select jsonb_path_query('[]', 'reduce($[*], (x, y) => x + y)'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[]', 'fold($[*], (x, y) => x + y, 100)'); + jsonb_path_query +------------------ + 100 +(1 row) + +select jsonb_path_query('[1]', 'reduce($[*], (x, y) => x + y)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])'); + jsonb_path_query +------------------- + [[[[], 1], 2], 3] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])'); + jsonb_path_query +------------------- + [[[[], 3], 2], 1] +(1 row) + +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + jsonb_path_query +------------------ + 1428 +(1 row) + +-- min/max item methods +select jsonb_path_query('1', 'strict $.min()'); +ERROR: SQL/JSON array not found +DETAIL: jsonpath .min() is applied to not an array +select jsonb_path_query('1', 'lax $.min()'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[]', '$.min()'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[]', '$.max()'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[null]', '$.min()'); + jsonb_path_query +------------------ + null +(1 row) + +select jsonb_path_query('[null]', '$.max()'); + jsonb_path_query +------------------ + null +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.min()'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.max()'); + jsonb_path_query +------------------ + 3 +(1 row) + +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()'); + jsonb_path_query +------------------ + 5 +(1 row) + +select jsonb_path_query('["aa", null, "a", "bbb"]', '$.min()'); + jsonb_path_query +------------------ + "a" +(1 row) + +select jsonb_path_query('["aa", null, "a", "bbb"]', '$.max()'); + jsonb_path_query +------------------ + "bbb" +(1 row) + +select jsonb_path_query('[1, null, "2"]', '$.max()'); +ERROR: SQL/JSON scalar required +DETAIL: boolean lambda expression in jsonpath .max() returned Unknown +-- min/max functions +select jsonb_path_query('1', 'strict min($)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('1', 'lax min($)'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[]', 'min($[*])'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[]', 'max($[*])'); + jsonb_path_query +------------------ +(0 rows) + +select jsonb_path_query('[null]', 'min($[*])'); + jsonb_path_query +------------------ + null +(1 row) + +select jsonb_path_query('[null]', 'max($[*])'); + jsonb_path_query +------------------ + null +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'min($[*])'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'max($[*])'); + jsonb_path_query +------------------ + 3 +(1 row) + +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])'); + jsonb_path_query +------------------ + 1 +(1 row) + +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])'); + jsonb_path_query +------------------ + 5 +(1 row) + +select jsonb_path_query('["aa", null, "a", "bbb"]', 'min($[*])'); + jsonb_path_query +------------------ + "a" +(1 row) + +select jsonb_path_query('["aa", null, "a", "bbb"]', 'max($[*])'); + jsonb_path_query +------------------ + "bbb" +(1 row) + +select jsonb_path_query('[1, null, "2"]', 'max($[*])'); +ERROR: SQL/JSON scalar required +DETAIL: boolean lambda expression in jsonpath .max() returned Unknown +-- tests for simplified variable-based lambda syntax +select jsonb_path_query('[1, 2, 3]', '$.map($1 + 100)'); + jsonb_path_query +------------------ + [101, 102, 103] +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'map($[*], $1 + 100)'); + jsonb_path_query +------------------ + 101 + 102 + 103 +(3 rows) + +select jsonb_path_query('[1, 2, 3]', '$.reduce($1 + $2)'); + jsonb_path_query +------------------ + 6 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)'); + jsonb_path_query +------------------ + 6 +(1 row) + +select jsonb_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)'); + jsonb_path_query +------------------ + 106 +(1 row) + +select jsonb_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)'); + jsonb_path_query +------------------ + 106 +(1 row) + +-- more complex tests +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]'); + jsonb_path_query +--------------------- + {"n": 0, "sum": 0} + {"n": 1, "sum": 1} + {"n": 2, "sum": 3} + {"n": 3, "sum": 6} + {"n": 4, "sum": 10} + {"n": 5, "sum": 15} + {"n": 6, "sum": 21} + {"n": 7, "sum": 28} + {"n": 8, "sum": 36} + {"n": 9, "sum": 45} +(10 rows) + +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]'); + jsonb_path_query +------------------- + {"n": 0, "s": 0} + {"n": 1, "s": 1} + {"n": 2, "s": 3} + {"n": 3, "s": 6} + {"n": 4, "s": 10} + {"n": 5, "s": 15} + {"n": 6, "s": 21} + {"n": 7, "s": 28} + {"n": 8, "s": 36} + {"n": 9, "s": 45} +(10 rows) + +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)'); + jsonb_path_query +------------------- + 61218243036424854 +(1 row) + +DROP EXTENSION jsonpathx; diff --git a/contrib/jsonpathx/jsonpathx--1.0.sql b/contrib/jsonpathx/jsonpathx--1.0.sql new file mode 100644 index 0000000000..0fb200fd9b --- /dev/null +++ b/contrib/jsonpathx/jsonpathx--1.0.sql @@ -0,0 +1,44 @@ +/* contrib/jsonpathx/jsonpathx--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION jsonpathx" to load this file. \quit + +CREATE FUNCTION map(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_map' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION flatmap(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_flatmap' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION reduce(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_reduce' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION fold(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_fold' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION foldl(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_foldl' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION foldr(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_foldr' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION min(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_min' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION max(jsonpath_fcxt) +RETURNS int8 +AS 'MODULE_PATHNAME', 'jsonpath_max' +LANGUAGE C STRICT STABLE; diff --git a/contrib/jsonpathx/jsonpathx.c b/contrib/jsonpathx/jsonpathx.c new file mode 100644 index 0000000000..c0b2fae900 --- /dev/null +++ b/contrib/jsonpathx/jsonpathx.c @@ -0,0 +1,842 @@ +/*------------------------------------------------------------------------- + * + * jsonpathx.c + * Extended jsonpath item methods and operators for jsonb type. + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/jsonpathx/jsonpathx.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "fmgr.h" +#include "nodes/pg_list.h" +#include "utils/builtins.h" +#include "utils/jsonpath.h" + +PG_MODULE_MAGIC; + +static JsonPathExecResult +jspThrowComparisonError(JsonPathExecContext *cxt, const char *methodName) +{ + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg(ERRMSG_JSON_SCALAR_REQUIRED), + errdetail("boolean lambda expression in jsonpath .%s() " + "returned Unknown", methodName))); + return jperError; +} + +static JsonPathExecResult +jspThrowSingletonRequiredError(JsonPathExecContext *cxt, const char *methodName) +{ + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED), + errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED), + errdetail("lambda expression in .%s() should return singleton item", + methodName))); + return jperError; +} + +static JsonPathExecResult +jspThrowArrayNotFoundError(JsonPathExecContext *cxt, const char *methodName) +{ + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND), + errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND), + errdetail("jsonpath .%s() is applied to not an array", + methodName))); + + return jperError; +} + +static JsonPathExecResult +jspThrowWrongArgumentsError(JsonPathExecContext *cxt, int req, int given, + const char *methodName) +{ + if (jspThrowErrors(cxt)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg(ERRMSG_JSON_SCALAR_REQUIRED), + errdetail("jsonpath .%s() requires %d arguments " + "but given %d", methodName, req, given))); + return jperError; +} + + +static JsonPathExecResult +jspExecuteSingleton(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonItem *jb, JsonItem **result) +{ + JsonValueList reslist = {0}; + JsonPathExecResult res = jspExecuteItem(cxt, jsp, jb, &reslist); + + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&reslist) != 1) + return jspThrowSingletonRequiredError(cxt, ""); + + *result = JsonValueListHead(&reslist); + + return jperOk; +} + +static JsonPathExecResult +jspMap(JsonPathFuncContext *fcxt, bool flat) +{ + JsonPathExecContext *cxt = fcxt->cxt; + JsonItem *jb = fcxt->item; + JsonPathItem *func = &fcxt->args[jb ? 0 : 1]; + void **funccache = &fcxt->argscache[jb ? 0 : 1]; + JsonPathExecResult res; + JsonItem *args[3]; + JsonItem jbvidx; + int index = 0; + int nargs = 1; + + if (fcxt->nargs != (jb ? 1 : 2)) + return jspThrowWrongArgumentsError(cxt, jb ? 1 : 2, fcxt->nargs, + fcxt->funcname); + + if (func->type == jpiLambda && func->content.lambda.nparams > 1) + { + args[nargs++] = &jbvidx; + JsonItemGetType(&jbvidx) = jbvNumeric; + } + + if (!jb) + { + JsonValueList items = {0}; + JsonValueListIterator iter; + JsonItem *item; + + res = jspExecuteItem(cxt, &fcxt->args[0], fcxt->jb, &items); + + if (jperIsError(res)) + return res; + + JsonValueListInitIterator(&items, &iter); + + while ((item = JsonValueListNext(&items, &iter))) + { + JsonValueList reslist = {0}; + + args[0] = item; + + if (nargs > 1) + { + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(index))); + index++; + } + + res = jspExecuteLambda(cxt, func, fcxt->jb, &reslist, + args, nargs, funccache); + if (jperIsError(res)) + return res; + + if (flat) + { + JsonValueListConcat(fcxt->result, reslist); + } + else + { + if (JsonValueListLength(&reslist) != 1) + return jspThrowSingletonRequiredError(cxt, fcxt->funcname); + + JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist)); + } + } + } + else if (JsonbType(jb) != jbvArray) + { + JsonValueList reslist = {0}; + JsonItemStackEntry entry; + + if (!jspAutoWrap(cxt)) + return jspThrowArrayNotFoundError(cxt, flat ? "flatmap" : "map"); + + args[0] = jb; + + if (nargs > 1) + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(0))); + + /* push additional stack entry for the whole item */ + pushJsonItem(&cxt->stack, &entry, jb, &cxt->baseObject); + res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache); + popJsonItem(&cxt->stack); + + if (jperIsError(res)) + return res; + + if (flat) + { + JsonValueListConcat(fcxt->result, reslist); + } + else + { + if (JsonValueListLength(&reslist) != 1) + return jspThrowSingletonRequiredError(cxt, fcxt->funcname); + + JsonValueListAppend(fcxt->result, JsonValueListHead(&reslist)); + } + } + else + { + JsonbValue elembuf; + JsonbValue *elem; + JsonxIterator it; + JsonbIteratorToken tok; + JsonValueList result = {0}; + JsonItemStackEntry entry; + int size = JsonxArraySize(jb, cxt->isJsonb); + int i; + bool isBinary = JsonItemIsBinary(jb); + + if (isBinary && size > 0) + { + elem = &elembuf; + JsonxIteratorInit(&it, JsonItemBinary(jb).data, cxt->isJsonb); + tok = JsonxIteratorNext(&it, &elembuf, false); + if (tok != WJB_BEGIN_ARRAY) + elog(ERROR, "unexpected jsonb token at the array start"); + } + + /* push additional stack entry for the whole array */ + pushJsonItem(&cxt->stack, &entry, jb, &cxt->baseObject); + + if (nargs > 1) + { + nargs = 3; + args[2] = jb; + } + + for (i = 0; i < size; i++) + { + JsonValueList reslist = {0}; + JsonItem elemjsi; + + if (isBinary) + { + tok = JsonxIteratorNext(&it, elem, true); + if (tok != WJB_ELEM) + break; + } + else + elem = &JsonItemArray(jb).elems[i]; + + args[0] = JsonbValueToJsonItem(elem, &elemjsi); + + if (nargs > 1) + { + JsonItemNumeric(&jbvidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(index))); + index++; + } + + res = jspExecuteLambda(cxt, func, jb, &reslist, args, nargs, funccache); + + if (jperIsError(res)) + { + popJsonItem(&cxt->stack); + return res; + } + + if (JsonValueListLength(&reslist) != 1) + { + popJsonItem(&cxt->stack); + return jspThrowSingletonRequiredError(cxt, fcxt->funcname); + } + + if (flat) + { + JsonItem *jsarr = JsonValueListHead(&reslist); + + if (JsonbType(jsarr) == jbvArray) + { + if (JsonItemIsBinary(jsarr)) + { + JsonxIterator it; + JsonbIteratorToken tok; + JsonbValue elem; + + JsonxIteratorInit(&it, JsonItemBinary(jsarr).data, cxt->isJsonb); + + while ((tok = JsonxIteratorNext(&it, &elem, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + { + JsonItem *jsi = palloc(sizeof(*jsi)); + + JsonValueListAppend(&result, + JsonbValueToJsonItem(&elem, jsi)); + } + } + } + else + { + JsonbValue *elem; + int size = JsonItemArray(jsarr).nElems; + int i = 0; + + for (i = 0; i < size; i++) + { + JsonItem *jsi = palloc(sizeof(*jsi)); + + elem = &JsonItemArray(jsarr).elems[i]; + + JsonValueListAppend(&result, + JsonbValueToJsonItem(elem, jsi)); + } + } + } + else if (jspAutoWrap(cxt)) + { + JsonValueListConcat(&result, reslist); + } + else + { + popJsonItem(&cxt->stack); + return jspThrowArrayNotFoundError(cxt, fcxt->funcname); + } + } + else + { + JsonValueListConcat(&result, reslist); + } + } + + popJsonItem(&cxt->stack); + + JsonAppendWrappedItems(fcxt->result, &result, cxt->isJsonb); + } + + return jperOk; +} + +PG_FUNCTION_INFO_V1(jsonpath_map); +Datum +jsonpath_map(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspMap((void *) PG_GETARG_POINTER(0), false)); +} + +PG_FUNCTION_INFO_V1(jsonpath_flatmap); +Datum +jsonpath_flatmap(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspMap((void *) PG_GETARG_POINTER(0), true)); +} + +typedef enum FoldType { FOLD_REDUCE, FOLD_LEFT, FOLD_RIGHT } FoldType; + +typedef struct FoldContext +{ + JsonPathExecContext *cxt; + JsonPathItem *func; + const char *funcname; + void **pfunccache; + JsonItem *item; + JsonItem *result; + JsonItem *args[4]; + JsonItem **argres; + JsonItem **argelem; + JsonItem argidx; + int nargs; +} FoldContext; + +static void +foldInit(FoldContext *fcxt, JsonPathExecContext *cxt, JsonPathItem *func, + void **pfunccache, JsonItem *array, JsonItem *item, + JsonItem *result, FoldType foldtype, const char *funcname) +{ + fcxt->cxt = cxt; + fcxt->func = func; + fcxt->pfunccache = pfunccache; + fcxt->item = item; + fcxt->result = result; + fcxt->funcname = funcname; + + if (foldtype == FOLD_RIGHT) + { + /* swap args for foldr() */ + fcxt->argres = &fcxt->args[1]; + fcxt->argelem = &fcxt->args[0]; + } + else + { + fcxt->argres = &fcxt->args[0]; + fcxt->argelem = &fcxt->args[1]; + } + + fcxt->nargs = 2; + + if (func->type == jpiLambda && func->content.lambda.nparams > 2) + { + fcxt->args[fcxt->nargs++] = &fcxt->argidx; + JsonItemGetType(&fcxt->argidx) = jbvNumeric; + if (array) + fcxt->args[fcxt->nargs++] = array; + } +} + +static JsonPathExecResult +foldAccumulate(FoldContext *fcxt, JsonItem *element, int index) +{ + JsonValueList reslist = {0}; + JsonPathExecResult res; + + if (!fcxt->result) /* first element of reduce */ + { + fcxt->result = element; + return jperOk; + } + + *fcxt->argres = fcxt->result; + *fcxt->argelem = element; + + if (fcxt->nargs > 2) + JsonItemNumeric(&fcxt->argidx) = DatumGetNumeric( + DirectFunctionCall1(int4_numeric, Int32GetDatum(index))); + + res = jspExecuteLambda(fcxt->cxt, fcxt->func, fcxt->item, &reslist, + fcxt->args, fcxt->nargs, fcxt->pfunccache); + + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&reslist) != 1) + return jspThrowSingletonRequiredError(fcxt->cxt, fcxt->funcname); + + fcxt->result = JsonValueListHead(&reslist); + + return jperOk; +} + +static JsonItem * +foldDone(FoldContext *fcxt) +{ + return fcxt->result; +} + +static void +list_reverse(List *itemlist) +{ + ListCell *curlc; + ListCell *prevlc = NULL; + + if (list_length(itemlist) <= 1) + return; + + curlc = itemlist->head; + itemlist->head = itemlist->tail; + itemlist->tail = curlc; + + while (curlc) + { + ListCell *next = curlc->next; + + curlc->next = prevlc; + prevlc = curlc; + curlc = next; + } +} + +static JsonPathExecResult +jspFoldSeq(JsonPathFuncContext *fcxt, FoldType ftype) +{ + FoldContext foldcxt; + JsonValueList items = {0}; + JsonItem *result = NULL; + JsonPathExecResult res; + int size; + + res = jspExecuteItem(fcxt->cxt, &fcxt->args[0], fcxt->jb, &items); + + if (jperIsError(res)) + return res; + + size = JsonValueListLength(&items); + + if (ftype == FOLD_REDUCE) + { + if (!size) + return jperNotFound; + + if (size == 1) + { + JsonValueListAppend(fcxt->result, JsonValueListHead(&items)); + return jperOk; + } + } + else + { + res = jspExecuteSingleton(fcxt->cxt, &fcxt->args[2], fcxt->jb, &result); + if (jperIsError(res)) + return res; + + if (!size) + { + JsonValueListAppend(fcxt->result, result); + return jperOk; + } + } + + foldInit(&foldcxt, fcxt->cxt, &fcxt->args[1], &fcxt->argscache[1], NULL, + fcxt->jb, result, ftype, fcxt->funcname); + + if (ftype == FOLD_RIGHT) + { + List *itemlist = JsonValueListGetList(&items); + ListCell *lc; + int index = list_length(itemlist) - 1; + + list_reverse(itemlist); + + foreach(lc, itemlist) + { + res = foldAccumulate(&foldcxt, lfirst(lc), index--); + + if (jperIsError(res)) + { + (void) foldDone(&foldcxt); + return res; + } + } + } + else + { + JsonValueListIterator iter; + JsonItem *item; + int index = 0; + + JsonValueListInitIterator(&items, &iter); + + while ((item = JsonValueListNext(&items, &iter))) + { + res = foldAccumulate(&foldcxt, item, index++); + + if (jperIsError(res)) + { + (void) foldDone(&foldcxt); + return res; + } + } + } + + result = foldDone(&foldcxt); + + JsonValueListAppend(fcxt->result, result); + + return jperOk; +} + +static JsonPathExecResult +jspFoldArray(JsonPathFuncContext *fcxt, FoldType ftype, JsonItem *item) +{ + JsonItem *result = NULL; + JsonPathExecResult res; + int size; + + if (JsonbType(item) != jbvArray) + { + if (!jspAutoWrap(fcxt->cxt)) + return jspThrowArrayNotFoundError(fcxt->cxt, fcxt->funcname); + + if (ftype == FOLD_REDUCE) + { + JsonValueListAppend(fcxt->result, item); + return jperOk; + } + + item = JsonWrapItemInArray(item, fcxt->cxt->isJsonb); + } + + size = JsonxArraySize(item, fcxt->cxt->isJsonb); + + if (ftype == FOLD_REDUCE) + { + if (!size) + return jperNotFound; + } + else + { + res = jspExecuteSingleton(fcxt->cxt, &fcxt->args[1], fcxt->jb, &result); + if (jperIsError(res)) + return res; + } + + if (ftype == FOLD_REDUCE && size == 1) + { + JsonbValue jbvresbuf; + JsonbValue *jbvres; + + result = palloc(sizeof(*result)); + + if (JsonItemIsBinary(item)) + { + jbvres = fcxt->cxt->isJsonb ? + getIthJsonbValueFromContainer(JsonItemBinary(item).data, 0, &jbvresbuf) : + getIthJsonValueFromContainer((JsonContainer *) JsonItemBinary(item).data, 0, &jbvresbuf); + + if (!jbvres) + return jperNotFound; + } + else + { + Assert(JsonItemIsArray(item)); + jbvres = &JsonItemArray(item).elems[0]; + } + + JsonbValueToJsonItem(jbvres, result); + } + else if (size) + { + FoldContext foldcxt; + JsonxIterator it; + JsonbIteratorToken tok; + JsonbValue elembuf; + JsonbValue *elem; + JsonItem itembuf; + int i; + bool foldr = ftype == FOLD_RIGHT; + bool useIter = false; + + if (JsonItemIsBinary(item)) + { + if (foldr) + { + /* unpack array for reverse iteration */ + JsonbParseState *ps = NULL; + JsonbValue *jbv = (fcxt->cxt->isJsonb ? pushJsonbValue : pushJsonValue) + (&ps, WJB_ELEM, JsonItemJbv(item)); + + item = JsonbValueToJsonItem(jbv, &itembuf); + } + else + { + elem = &elembuf; + useIter = true; + JsonxIteratorInit(&it, JsonItemBinary(item).data, fcxt->cxt->isJsonb); + tok = JsonxIteratorNext(&it, elem, false); + if (tok != WJB_BEGIN_ARRAY) + elog(ERROR, "unexpected jsonb token at the array start"); + } + } + + foldInit(&foldcxt, fcxt->cxt, &fcxt->args[0], &fcxt->argscache[0], + item, fcxt->jb, result, ftype, fcxt->funcname); + + for (i = 0; i < size; i++) + { + JsonItem elbuf; + JsonItem *el; + int index; + + if (useIter) + { + tok = JsonxIteratorNext(&it, elem, true); + if (tok != WJB_ELEM) + break; + index = i; + } + else + { + index = foldr ? size - i - 1 : i; + elem = &JsonItemArray(item).elems[index]; + } + + el = JsonbValueToJsonItem(elem, &elbuf); + + if (!i && ftype == FOLD_REDUCE) + el = copyJsonItem(el); + + res = foldAccumulate(&foldcxt, el, index); + + if (jperIsError(res)) + { + (void) foldDone(&foldcxt); + return res; + } + } + + result = foldDone(&foldcxt); + } + + JsonValueListAppend(fcxt->result, result); + + return jperOk; +} + +static JsonPathExecResult +jspFold(JsonPathFuncContext *fcxt, FoldType ftype, const char *funcName) +{ + JsonItem *item = fcxt->item; + int nargs = (ftype == FOLD_REDUCE ? 1 : 2) + (item ? 0 : 1); + + if (fcxt->nargs != nargs) + return jspThrowWrongArgumentsError(fcxt->cxt, nargs, fcxt->nargs, funcName); + + return item ? jspFoldArray(fcxt, ftype, item) : jspFoldSeq(fcxt, ftype); +} + +PG_FUNCTION_INFO_V1(jsonpath_reduce); +Datum +jsonpath_reduce(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_REDUCE, "reduce")); +} + +PG_FUNCTION_INFO_V1(jsonpath_fold); +Datum +jsonpath_fold(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_LEFT, "fold")); +} + +PG_FUNCTION_INFO_V1(jsonpath_foldl); +Datum +jsonpath_foldl(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_LEFT, "foldl")); +} + +PG_FUNCTION_INFO_V1(jsonpath_foldr); +Datum +jsonpath_foldr(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspFold((void *) PG_GETARG_POINTER(0), FOLD_RIGHT, "foldr")); +} + +static JsonPathExecResult +jspMinMax(JsonPathFuncContext *fcxt, bool max, const char *funcName) +{ + JsonItem *item = fcxt->item; + JsonItem *result = NULL; + JsonPathItemType cmpop = max ? jpiGreater : jpiLess; + + if (fcxt->nargs != (item ? 0 : 1)) + return jspThrowWrongArgumentsError(fcxt->cxt, item ? 0 : 1, fcxt->nargs, + funcName); + + if (!item) + { + JsonValueList items = {0}; + JsonValueListIterator iter; + JsonItem *item; + JsonPathExecResult res; + + res = jspExecuteItem(fcxt->cxt, &fcxt->args[0], fcxt->jb, &items); + if (jperIsError(res)) + return res; + + if (!JsonValueListLength(&items)) + return jperNotFound; + + JsonValueListInitIterator(&items, &iter); + + while ((item = JsonValueListNext(&items, &iter))) + { + if (result) + { + JsonPathBool cmp = jspCompareItems(cmpop, item, result); + + if (cmp == jpbUnknown) + return jspThrowComparisonError(fcxt->cxt, funcName); + + if (cmp == jpbTrue) + result = item; + } + else + { + result = item; + } + } + } + else if (JsonbType(item) != jbvArray) + { + if (!jspAutoWrap(fcxt->cxt)) + return jspThrowArrayNotFoundError(fcxt->cxt, funcName); + + result = item; + } + else + { + JsonbValue elmebuf; + JsonbValue *elem; + JsonxIterator it; + JsonbIteratorToken tok; + int size = JsonxArraySize(item, fcxt->cxt->isJsonb); + int i; + bool isBinary = JsonItemIsBinary(item); + + if (isBinary) + { + elem = &elmebuf; + JsonxIteratorInit(&it, JsonItemBinary(item).data, fcxt->cxt->isJsonb); + tok = JsonxIteratorNext(&it, &elmebuf, false); + if (tok != WJB_BEGIN_ARRAY) + elog(ERROR, "unexpected jsonb token at the array start"); + } + + for (i = 0; i < size; i++) + { + JsonItem elemjsi; + + if (isBinary) + { + tok = JsonxIteratorNext(&it, elem, true); + if (tok != WJB_ELEM) + break; + } + else + elem = &JsonItemArray(item).elems[i]; + + if (!i) + { + result = palloc(sizeof(*result)); + JsonbValueToJsonItem(elem, result); + } + else + { + JsonPathBool cmp = jspCompareItems(cmpop, + JsonbValueToJsonItem(elem, &elemjsi), + result); + + if (cmp == jpbUnknown) + return jspThrowComparisonError(fcxt->cxt, funcName); + + if (cmp == jpbTrue) + *result = elemjsi; + } + } + + if (!result) + return jperNotFound; + } + + JsonValueListAppend(fcxt->result, result); + return jperOk; +} + +PG_FUNCTION_INFO_V1(jsonpath_min); +Datum +jsonpath_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspMinMax((void *) PG_GETARG_POINTER(0), false, "min")); +} + +PG_FUNCTION_INFO_V1(jsonpath_max); +Datum +jsonpath_max(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(jspMinMax((void *) PG_GETARG_POINTER(0), true, "max")); +} diff --git a/contrib/jsonpathx/jsonpathx.control b/contrib/jsonpathx/jsonpathx.control new file mode 100644 index 0000000000..cc67bd05ff --- /dev/null +++ b/contrib/jsonpathx/jsonpathx.control @@ -0,0 +1,5 @@ +# jsonpathx extension +comment = 'extended JSON path item methods' +default_version = '1.0' +module_pathname = '$libdir/jsonpathx' +relocatable = true diff --git a/contrib/jsonpathx/sql/jsonpathx_json.sql b/contrib/jsonpathx/sql/jsonpathx_json.sql new file mode 100644 index 0000000000..26f8f479f5 --- /dev/null +++ b/contrib/jsonpathx/sql/jsonpathx_json.sql @@ -0,0 +1,89 @@ +CREATE EXTENSION jsonpathx; + +-- map item method +select json_path_query('1', 'strict $.map(x => x + 10)'); +select json_path_query('1', 'lax $.map(x => x + 10)'); +select json_path_query('[1, 2, 3]', '$.map(x => x + 10)'); +select json_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))'); + +-- map function +select json_path_query('1', 'strict map($, x => x + 10)'); +select json_path_query('1', 'lax map($, x => x + 10)'); +select json_path_query('[1, 2, 3]', 'map($[*], x => x + 10)'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))'); + +-- reduce/fold item methods +select json_path_query('1', 'strict $.reduce((x, y) => x + y)'); +select json_path_query('1', 'lax $.reduce((x, y) => x + y)'); +select json_path_query('1', 'strict $.fold((x, y) => x + y, 10)'); +select json_path_query('1', 'lax $.fold((x, y) => x + y, 10)'); +select json_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)'); +select json_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)'); +select json_path_query('[]', '$.reduce((x, y) => x + y)'); +select json_path_query('[]', '$.fold((x, y) => x + y, 100)'); +select json_path_query('[1]', '$.reduce((x, y) => x + y)'); +select json_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])'); +select json_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + +-- reduce/fold functions +select json_path_query('1', 'strict reduce($, (x, y) => x + y)'); +select json_path_query('1', 'lax reduce($, (x, y) => x + y)'); +select json_path_query('1', 'strict fold($, (x, y) => x + y, 10)'); +select json_path_query('1', 'lax fold($, (x, y) => x + y, 10)'); +select json_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)'); +select json_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)'); +select json_path_query('[]', 'reduce($[*], (x, y) => x + y)'); +select json_path_query('[]', 'fold($[*], (x, y) => x + y, 100)'); +select json_path_query('[1]', 'reduce($[*], (x, y) => x + y)'); +select json_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])'); +select json_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])'); +select json_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + +-- min/max item methods +select json_path_query('1', 'strict $.min()'); +select json_path_query('1', 'lax $.min()'); +select json_path_query('[]', '$.min()'); +select json_path_query('[]', '$.max()'); +select json_path_query('[null]', '$.min()'); +select json_path_query('[null]', '$.max()'); +select json_path_query('[1, 2, 3]', '$.min()'); +select json_path_query('[1, 2, 3]', '$.max()'); +select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()'); +select json_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()'); +select json_path_query('["aa", null, "a", "bbb"]', '$.min()'); +select json_path_query('["aa", null, "a", "bbb"]', '$.max()'); +select json_path_query('[1, null, "2"]', '$.max()'); + +-- min/max functions +select json_path_query('1', 'strict min($)'); +select json_path_query('1', 'lax min($)'); +select json_path_query('[]', 'min($[*])'); +select json_path_query('[]', 'max($[*])'); +select json_path_query('[null]', 'min($[*])'); +select json_path_query('[null]', 'max($[*])'); +select json_path_query('[1, 2, 3]', 'min($[*])'); +select json_path_query('[1, 2, 3]', 'max($[*])'); +select json_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])'); +select json_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])'); +select json_path_query('["aa", null, "a", "bbb"]', 'min($[*])'); +select json_path_query('["aa", null, "a", "bbb"]', 'max($[*])'); +select json_path_query('[1, null, "2"]', 'max($[*])'); + +-- tests for simplified variable-based lambda syntax +select json_path_query('[1, 2, 3]', '$.map($1 + 100)'); +select json_path_query('[1, 2, 3]', 'map($[*], $1 + 100)'); +select json_path_query('[1, 2, 3]', '$.reduce($1 + $2)'); +select json_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)'); +select json_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)'); +select json_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)'); + +-- more complex tests +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]'); +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]'); +select json_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)'); + +DROP EXTENSION jsonpathx; diff --git a/contrib/jsonpathx/sql/jsonpathx_jsonb.sql b/contrib/jsonpathx/sql/jsonpathx_jsonb.sql new file mode 100644 index 0000000000..bee7ee7833 --- /dev/null +++ b/contrib/jsonpathx/sql/jsonpathx_jsonb.sql @@ -0,0 +1,89 @@ +CREATE EXTENSION jsonpathx; + +-- map item method +select jsonb_path_query('1', 'strict $.map(x => x + 10)'); +select jsonb_path_query('1', 'lax $.map(x => x + 10)'); +select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)'); +select jsonb_path_query('[1, 2, 3]', '$.map(x => x + 10)[*]'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.map(a => a.map(x => x + 10))'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.flatmap(a => a.map(a => a + 10))'); + +-- map function +select jsonb_path_query('1', 'strict map($, x => x + 10)'); +select jsonb_path_query('1', 'lax map($, x => x + 10)'); +select jsonb_path_query('[1, 2, 3]', 'map($[*], x => x + 10)'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'map($[*], x => [map(x[*], x => x + 10)])'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'flatmap($[*], a => map(a[*], x => x + 10))'); + +-- reduce/fold item methods +select jsonb_path_query('1', 'strict $.reduce((x, y) => x + y)'); +select jsonb_path_query('1', 'lax $.reduce((x, y) => x + y)'); +select jsonb_path_query('1', 'strict $.fold((x, y) => x + y, 10)'); +select jsonb_path_query('1', 'lax $.fold((x, y) => x + y, 10)'); +select jsonb_path_query('[1, 2, 3]', '$.reduce((x, y) => x + y)'); +select jsonb_path_query('[1, 2, 3]', '$.fold((x, y) => x + y, 100)'); +select jsonb_path_query('[]', '$.reduce((x, y) => x + y)'); +select jsonb_path_query('[]', '$.fold((x, y) => x + y, 100)'); +select jsonb_path_query('[1]', '$.reduce((x, y) => x + y)'); +select jsonb_path_query('[1, 2, 3]', '$.foldl((x, y) => [x, y], [])'); +select jsonb_path_query('[1, 2, 3]', '$.foldr((x, y) => [y, x], [])'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '$.fold((x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + +-- reduce/fold functions +select jsonb_path_query('1', 'strict reduce($, (x, y) => x + y)'); +select jsonb_path_query('1', 'lax reduce($, (x, y) => x + y)'); +select jsonb_path_query('1', 'strict fold($, (x, y) => x + y, 10)'); +select jsonb_path_query('1', 'lax fold($, (x, y) => x + y, 10)'); +select jsonb_path_query('[1, 2, 3]', 'reduce($[*], (x, y) => x + y)'); +select jsonb_path_query('[1, 2, 3]', 'fold($[*], (x, y) => x + y, 100)'); +select jsonb_path_query('[]', 'reduce($[*], (x, y) => x + y)'); +select jsonb_path_query('[]', 'fold($[*], (x, y) => x + y, 100)'); +select jsonb_path_query('[1]', 'reduce($[*], (x, y) => x + y)'); +select jsonb_path_query('[1, 2, 3]', 'foldl($[*], (x, y) => [x, y], [])'); +select jsonb_path_query('[1, 2, 3]', 'foldr($[*], (x, y) => [y, x], [])'); +select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'fold($[*], (x, y) => x + y.fold((a, b) => a + b, 100), 1000)'); + +-- min/max item methods +select jsonb_path_query('1', 'strict $.min()'); +select jsonb_path_query('1', 'lax $.min()'); +select jsonb_path_query('[]', '$.min()'); +select jsonb_path_query('[]', '$.max()'); +select jsonb_path_query('[null]', '$.min()'); +select jsonb_path_query('[null]', '$.max()'); +select jsonb_path_query('[1, 2, 3]', '$.min()'); +select jsonb_path_query('[1, 2, 3]', '$.max()'); +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.min()'); +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', '$.max()'); +select jsonb_path_query('["aa", null, "a", "bbb"]', '$.min()'); +select jsonb_path_query('["aa", null, "a", "bbb"]', '$.max()'); +select jsonb_path_query('[1, null, "2"]', '$.max()'); + +-- min/max functions +select jsonb_path_query('1', 'strict min($)'); +select jsonb_path_query('1', 'lax min($)'); +select jsonb_path_query('[]', 'min($[*])'); +select jsonb_path_query('[]', 'max($[*])'); +select jsonb_path_query('[null]', 'min($[*])'); +select jsonb_path_query('[null]', 'max($[*])'); +select jsonb_path_query('[1, 2, 3]', 'min($[*])'); +select jsonb_path_query('[1, 2, 3]', 'max($[*])'); +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'min($[*])'); +select jsonb_path_query('[2, 3, 5, null, 1, 4, null]', 'max($[*])'); +select jsonb_path_query('["aa", null, "a", "bbb"]', 'min($[*])'); +select jsonb_path_query('["aa", null, "a", "bbb"]', 'max($[*])'); +select jsonb_path_query('[1, null, "2"]', 'max($[*])'); + +-- tests for simplified variable-based lambda syntax +select jsonb_path_query('[1, 2, 3]', '$.map($1 + 100)'); +select jsonb_path_query('[1, 2, 3]', 'map($[*], $1 + 100)'); +select jsonb_path_query('[1, 2, 3]', '$.reduce($1 + $2)'); +select jsonb_path_query('[1, 2, 3]', 'reduce($[*], $1 + $2)'); +select jsonb_path_query('[1, 2, 3]', '$.fold($1 + $2, 100)'); +select jsonb_path_query('[1, 2, 3]', 'fold($[*], $1 + $2, 100)'); + +-- more complex tests +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.map((x,i,a) => {n: i, sum: reduce(a[0 to i], (x,y) => x + y)})[*]'); +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y,i,a) => [x[*], {n:y, s: [a[0 to i]].reduce($1+$2)}], [])[*]'); +select jsonb_path_query('[0,1,2,3,4,5,6,7,8,9]', '$.fold((x,y) => [y,y,y].map((a) => a + y).reduce((x,y)=>x+y) + x * 100, 0)'); + +DROP EXTENSION jsonpathx;