From bd552cc22171a3993e6d18d61f53fb2caf21ccf6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 19:38:32 +0300 Subject: [PATCH 01/97] Add strict mode for do_to_timestamp() --- src/backend/utils/adt/formatting.c | 36 ++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index a345c65605..112356be9f 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -960,7 +960,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); @@ -977,7 +978,7 @@ 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, +static void do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm, fsec_t *fsec); static char *fill_str(char *str, int c, int max); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); @@ -2973,13 +2974,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 when trailing input characters or format + * nodes remain after parsing. * * 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; @@ -3261,6 +3264,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out) break; } } + + 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 == ' ') + s++; + + if (*s != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string after " + "datetime format"))); + } } /* select a DCHCacheEntry to hold the given format picture */ @@ -3562,7 +3582,7 @@ to_timestamp(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); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3597,7 +3617,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); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3630,9 +3650,11 @@ to_date(PG_FUNCTION_ARGS) * * The TmFromChar is then analysed and converted into the final results in * struct 'tm' and 'fsec'. + * 'strict' enables error reporting when trailing characters remain in input or + * format strings after parsing. */ static void -do_to_timestamp(text *date_txt, text *fmt, +do_to_timestamp(text *date_txt, text *fmt, bool strict, struct pg_tm *tm, fsec_t *fsec) { FormatNode *format; @@ -3686,7 +3708,7 @@ 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 (!incache) From 8244cbe285a718f20b34aa99e23a68b97070894d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 23:26:42 +0300 Subject: [PATCH 02/97] Pass cstring with its length to do_to_timestamp() instead of text --- src/backend/utils/adt/formatting.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 112356be9f..4fec276cc5 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -978,8 +978,8 @@ 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, bool strict, - struct pg_tm *tm, fsec_t *fsec); +static void do_to_timestamp(text *date_txt, const char *fmt, int fmt_len, + bool strict, struct pg_tm *tm, fsec_t *fsec); 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); @@ -3582,7 +3582,8 @@ to_timestamp(PG_FUNCTION_ARGS) struct pg_tm tm; fsec_t fsec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec); + do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, + &tm, &fsec); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3617,7 +3618,8 @@ to_date(PG_FUNCTION_ARGS) struct pg_tm tm; fsec_t fsec; - do_to_timestamp(date_txt, fmt, false, &tm, &fsec); + do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, + &tm, &fsec); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3654,12 +3656,12 @@ to_date(PG_FUNCTION_ARGS) * format strings after parsing. */ static void -do_to_timestamp(text *date_txt, text *fmt, bool strict, +do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict, struct pg_tm *tm, fsec_t *fsec) { FormatNode *format; TmFromChar tmfc; - int fmt_len; + char *fmt_tmp = NULL; char *date_str; int fmask; @@ -3670,15 +3672,15 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, *fsec = 0; fmask = 0; /* bit mask for ValidateDate() */ - fmt_len = VARSIZE_ANY_EXHDR(fmt); + if (fmt_len < 0) /* zero-terminated */ + fmt_len = strlen(fmt_str); + else if (fmt_len > 0) /* not zero-terminated */ + fmt_str = fmt_tmp = pnstrdup(fmt_str, fmt_len); if (fmt_len) { - char *fmt_str; bool incache; - fmt_str = text_to_cstring(fmt); - if (fmt_len > DCH_CACHE_SIZE) { /* @@ -3710,11 +3712,13 @@ do_to_timestamp(text *date_txt, text *fmt, bool strict, DCH_from_char(format, date_str, &tmfc, strict); - pfree(fmt_str); if (!incache) pfree(format); } + if (fmt_tmp) + pfree(fmt_tmp); + DEBUG_TMFC(&tmfc); /* From c72ba9ecae400b31fb30e7803a0f72c02f11e524 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 23:31:01 +0300 Subject: [PATCH 03/97] Add to_datetime() --- src/backend/utils/adt/date.c | 11 +- src/backend/utils/adt/formatting.c | 266 ++++++++++++++++++++++++++++- 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 + 6 files changed, 274 insertions(+), 14 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 87146a2161..bf75332b5b 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) @@ -1260,7 +1255,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) @@ -1426,7 +1421,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] = { @@ -2004,7 +1999,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 4fec276cc5..a9c9bb7279 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -87,6 +87,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" @@ -946,6 +947,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 @@ -979,7 +984,7 @@ 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, const char *fmt, int fmt_len, - bool strict, struct pg_tm *tm, fsec_t *fsec); + bool strict, struct pg_tm *tm, fsec_t *fsec, 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); @@ -3283,6 +3288,103 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) } } +/* Get mask of date/time/zone formatting 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_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) @@ -3583,7 +3685,7 @@ to_timestamp(PG_FUNCTION_ARGS) fsec_t fsec; do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, - &tm, &fsec); + &tm, &fsec, NULL); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3619,7 +3721,7 @@ to_date(PG_FUNCTION_ARGS) fsec_t fsec; do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, - &tm, &fsec); + &tm, &fsec, NULL); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3640,6 +3742,155 @@ 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, const char *fmt, int fmt_len, bool strict, + Oid *typid, int32 *typmod) +{ + struct pg_tm tm; + fsec_t fsec; + int flags; + + do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags); + + *typmod = -1; /* TODO implement FF1, ..., FF9 */ + + if (flags & DCH_DATED) + { + if (flags & DCH_TIMED) + { + if (flags & DCH_ZONED) + { + TimestampTz result; + int tz; + + if (tm.tm_zone) + { + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); + + if (dterr) + DateTimeParseError(dterr, text_to_cstring(date_txt), + "timestamptz"); + } + else + 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)); + int tz; + + if (tm.tm_zone) + { + int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); + + if (dterr) + DateTimeParseError(dterr, text_to_cstring(date_txt), + "timetz"); + } + else + 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 * @@ -3652,12 +3903,16 @@ 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 formatting components found in 'fmt_str' is + * returned in 'flags'. + * * 'strict' enables error reporting when trailing characters remain in input or * format strings after parsing. */ static void do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict, - struct pg_tm *tm, fsec_t *fsec) + struct pg_tm *tm, fsec_t *fsec, int *flags) { FormatNode *format; TmFromChar tmfc; @@ -3712,6 +3967,9 @@ do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict, DCH_from_char(format, date_str, &tmfc, strict); + if (flags) + *flags = DCH_datetime_type(format); + if (!incache) pfree(format); } diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 1d75caebe1..431644dd1d 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); @@ -330,7 +329,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 eb6d2a16fe..10cc822752 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 d66582b7a2..d3dd851437 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 a9f5548b46..208cc000d3 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, const char *fmt, int fmt_len, + bool strict, Oid *typid, int32 *typmod); + #endif From 374b6fd10e9b5ad79ea404fae9e2c36241bfbb4d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 16 Feb 2017 12:32:00 +0300 Subject: [PATCH 04/97] Add SQL/JSON SQLSTATE errcodes --- src/backend/utils/errcodes.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt index e2976600e8..790be843d8 100644 --- a/src/backend/utils/errcodes.txt +++ b/src/backend/utils/errcodes.txt @@ -206,6 +206,22 @@ Section: Class 22 - Data Exception 2200N E ERRCODE_INVALID_XML_CONTENT invalid_xml_content 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 +22035 E ERRCODE_NO_JSON_ITEM no_json_item +22036 E ERRCODE_NON_NUMERIC_JSON_ITEM non_numeric_json_item +22037 E ERRCODE_NON_UNIQUE_KEYS_IN_JSON_OBJECT non_unique_keys_in_json_object +22038 E ERRCODE_SINGLETON_JSON_ITEM_REQUIRED singleton_json_item_required +22039 E ERRCODE_JSON_ARRAY_NOT_FOUND json_array_not_found +2203A E ERRCODE_JSON_MEMBER_NOT_FOUND json_member_not_found +2203B E ERRCODE_JSON_NUMBER_NOT_FOUND json_number_not_found +2203C E ERRCODE_JSON_OBJECT_NOT_FOUND object_not_found +2203F E ERRCODE_JSON_SCALAR_REQUIRED json_scalar_required +2203D E ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS too_many_json_array_elements +2203E E ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS too_many_json_object_members Section: Class 23 - Integrity Constraint Violation From 85a721de33927117d964700af853da53487c92ad Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 14:21:50 +0300 Subject: [PATCH 05/97] Initial jsonpath support Main TODO - indexed arrays [1,4, 6 to 18] - variables execution ( $varname ) - function support - ariphmetic support - correct error support It provides two debugging functions: _jsonpath_object and _jsonpath_exist Some examples are avaliable in src/test/regress/sql/sql_json.sql --- src/backend/Makefile | 11 +- src/backend/lib/stringinfo.c | 21 + src/backend/utils/adt/Makefile | 23 +- src/backend/utils/adt/jsonb.c | 6 +- src/backend/utils/adt/jsonpath.c | 415 +++++++++++++++ src/backend/utils/adt/jsonpath_exec.c | 420 +++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 278 ++++++++++ src/backend/utils/adt/jsonpath_scan.l | 513 +++++++++++++++++++ src/include/catalog/pg_proc.dat | 15 + src/include/catalog/pg_type.dat | 10 + src/include/lib/stringinfo.h | 6 + src/include/utils/jsonb.h | 3 + src/include/utils/jsonpath.h | 156 ++++++ src/include/utils/jsonpath_scanner.h | 30 ++ src/test/regress/expected/jsonb_jsonpath.out | 92 ++++ src/test/regress/expected/jsonpath.out | 425 +++++++++++++++ src/test/regress/parallel_schedule | 7 +- src/test/regress/serial_schedule | 2 + src/test/regress/sql/jsonb_jsonpath.sql | 16 + src/test/regress/sql/jsonpath.sql | 76 +++ 20 files changed, 2519 insertions(+), 6 deletions(-) create mode 100644 src/backend/utils/adt/jsonpath.c create mode 100644 src/backend/utils/adt/jsonpath_exec.c create mode 100644 src/backend/utils/adt/jsonpath_gram.y create mode 100644 src/backend/utils/adt/jsonpath_scan.l create mode 100644 src/include/utils/jsonpath.h create mode 100644 src/include/utils/jsonpath_scanner.h create mode 100644 src/test/regress/expected/jsonb_jsonpath.out create mode 100644 src/test/regress/expected/jsonpath.out create mode 100644 src/test/regress/sql/jsonb_jsonpath.sql create mode 100644 src/test/regress/sql/jsonpath.sql diff --git a/src/backend/Makefile b/src/backend/Makefile index 25af514fba..b0f6549a81 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -136,6 +136,9 @@ parser/gram.h: parser/gram.y storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lwlocknames.txt $(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c +utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y + $(MAKE) -C utils/adt jsonpath_gram.h + # run this unconditionally to avoid needing to know its dependencies here: submake-catalog-headers: $(MAKE) -C catalog distprep generated-header-symlinks @@ -159,7 +162,7 @@ submake-utils-headers: .PHONY: generated-headers -generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers +generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/jsonpath_gram.h submake-catalog-headers submake-utils-headers $(top_builddir)/src/include/parser/gram.h: parser/gram.h prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \ @@ -171,6 +174,10 @@ $(top_builddir)/src/include/storage/lwlocknames.h: storage/lmgr/lwlocknames.h cd '$(dir $@)' && rm -f $(notdir $@) && \ $(LN_S) "$$prereqdir/$(notdir $<)" . +$(top_builddir)/src/include/utils/jsonpath_gram.h: utils/adt/jsonpath_gram.h + prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \ + cd '$(dir $@)' && rm -f $(notdir $@) && \ + $(LN_S) "$$prereqdir/$(notdir $<)" . utils/probes.o: utils/probes.d $(SUBDIROBJS) $(DTRACE) $(DTRACEFLAGS) -C -G -s $(call expand_subsys,$^) -o $@ @@ -186,6 +193,7 @@ distprep: $(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c $(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c $(MAKE) -C utils distprep + $(MAKE) -C utils/adt jsonpath_gram.c jsonpath_gram.h jsonpath_scan.c $(MAKE) -C utils/misc guc-file.c $(MAKE) -C utils/sort qsort_tuple.c @@ -310,6 +318,7 @@ maintainer-clean: distclean storage/lmgr/lwlocknames.c \ storage/lmgr/lwlocknames.h \ utils/misc/guc-file.c \ + utils/adt/jsonpath_gram.h \ utils/sort/qsort_tuple.c diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c index 798a823ac9..da0c098f31 100644 --- a/src/backend/lib/stringinfo.c +++ b/src/backend/lib/stringinfo.c @@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed) str->maxlen = newlen; } + +/* + * alignStringInfoInt - aling StringInfo to int by adding + * zero padding bytes + */ +void +alignStringInfoInt(StringInfo buf) +{ + switch(INTALIGN(buf->len) - buf->len) + { + case 3: + appendStringInfoCharMacro(buf, 0); + case 2: + appendStringInfoCharMacro(buf, 0); + case 1: + appendStringInfoCharMacro(buf, 0); + default: + break; + } +} + diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 4b35dbb8bb..41b00fd4cd 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -17,7 +17,8 @@ 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 like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \ + jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \ + like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \ @@ -32,6 +33,26 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \ txid.o uuid.o varbit.o varchar.o varlena.o version.o \ windowfuncs.o xid.o xml.o +# Latest flex causes warnings in this file. +ifeq ($(GCC),yes) +scan.o: CFLAGS += -Wno-error +endif + +jsonpath_gram.c: BISONFLAGS += -d + +jsonpath_scan.c: FLEXFLAGS = -CF -p -p + +jsonpath_gram.h: jsonpath_gram.c ; + +# Force these dependencies to be known even without dependency info built: +jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h + +# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution +# tarball, so they are not cleaned here. +clean distclean maintainer-clean: + rm -f lex.backup + + like.o: like.c like_match.c varlena.o: varlena.c levenshtein.c diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index 6940b11c29..4c79a5e3d3 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -1857,7 +1857,7 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS) /* * Extract scalar value from raw-scalar pseudo-array jsonb. */ -static bool +JsonbValue * JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) { JsonbIterator *it; @@ -1868,7 +1868,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) { /* inform caller about actual type of container */ res->type = (JsonContainerIsArray(jbc)) ? jbvArray : jbvObject; - return false; + return NULL; } /* @@ -1891,7 +1891,7 @@ JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res) tok = JsonbIteratorNext(&it, &tmp, true); Assert(tok == WJB_DONE); - return true; + return res; } /* diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c new file mode 100644 index 0000000000..8ac882c7e0 --- /dev/null +++ b/src/backend/utils/adt/jsonpath.c @@ -0,0 +1,415 @@ +/*------------------------------------------------------------------------- + * + * jsonpath.c + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonpath.h" + +static int +flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) +{ + int32 pos = buf->len - VARHDRSZ; /* position from begining of jsonpath data */ + int32 chld, next; + + check_stack_depth(); + + appendStringInfoChar(buf, (char)(item->type)); + alignStringInfoInt(buf); + + next = (item->next) ? buf->len : 0; + appendBinaryStringInfo(buf, (char*)&next /* fake value */, sizeof(next)); + + switch(item->type) + { + case jpiKey: + case jpiString: + case jpiVariable: + appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len)); + appendBinaryStringInfo(buf, item->string.val, item->string.len); + appendStringInfoChar(buf, '\0'); + break; + case jpiNumeric: + appendBinaryStringInfo(buf, (char*)item->numeric, VARSIZE(item->numeric)); + break; + case jpiBool: + appendBinaryStringInfo(buf, (char*)&item->boolean, sizeof(item->boolean)); + break; + case jpiAnd: + case jpiOr: + { + int32 left, right; + + left = buf->len; + appendBinaryStringInfo(buf, (char*)&left /* fake value */, sizeof(left)); + right = buf->len; + appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); + + chld = flattenJsonPathParseItem(buf, item->args.left); + *(int32*)(buf->data + left) = chld; + chld = flattenJsonPathParseItem(buf, item->args.right); + *(int32*)(buf->data + right) = chld; + } + break; + case jpiEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiNot: + case jpiExpression: + { + int32 arg; + + arg = buf->len; + appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); + + chld = flattenJsonPathParseItem(buf, item->arg); + *(int32*)(buf->data + arg) = chld; + } + break; + case jpiAnyArray: + case jpiAnyKey: + case jpiCurrent: + case jpiRoot: + case jpiNull: + break; + default: + elog(ERROR, "Unknown jsonpath item type: %d", item->type); + } + + if (item->next) + *(int32*)(buf->data + next) = flattenJsonPathParseItem(buf, item->next); + + return pos; +} + +Datum +jsonpath_in(PG_FUNCTION_ARGS) +{ + char *in = PG_GETARG_CSTRING(0); + int32 len = strlen(in); + JsonPathParseItem *jsonpath = parsejsonpath(in, len); + JsonPath *res; + StringInfoData buf; + + initStringInfo(&buf); + enlargeStringInfo(&buf, 4 * len /* estimation */); + + appendStringInfoSpaces(&buf, VARHDRSZ); + + if (!jsonpath) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for jsonpath: \"%s\"", in))); + + flattenJsonPathParseItem(&buf, jsonpath); + + res = (JsonPath*)buf.data; + SET_VARSIZE(res, buf.len); + + PG_RETURN_JSONPATH_P(res); +} + +static void +printOperation(StringInfo buf, JsonPathItemType type) +{ + switch(type) + { + case jpiAnd: + appendBinaryStringInfo(buf, " && ", 4); break; + case jpiOr: + appendBinaryStringInfo(buf, " || ", 4); break; + case jpiEqual: + appendBinaryStringInfo(buf, " = ", 3); break; + case jpiLess: + appendBinaryStringInfo(buf, " < ", 3); break; + case jpiGreater: + appendBinaryStringInfo(buf, " > ", 3); break; + case jpiLessOrEqual: + appendBinaryStringInfo(buf, " <= ", 4); break; + case jpiGreaterOrEqual: + appendBinaryStringInfo(buf, " >= ", 4); break; + default: + elog(ERROR, "Unknown jsonpath item type: %d", type); + } +} + +static void +printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes) +{ + JsonPathItem elem; + + check_stack_depth(); + + switch(v->type) + { + case jpiNull: + appendStringInfoString(buf, "null"); + break; + case jpiKey: + if (inKey) + appendStringInfoChar(buf, '.'); + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiString: + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiVariable: + appendStringInfoChar(buf, '$'); + escape_json(buf, jspGetString(v, NULL)); + break; + case jpiNumeric: + appendStringInfoString(buf, + DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(jspGetNumeric(v))))); + break; + case jpiBool: + if (jspGetBool(v)) + appendBinaryStringInfo(buf, "true", 4); + else + appendBinaryStringInfo(buf, "false", 5); + break; + case jpiAnd: + case jpiOr: + appendStringInfoChar(buf, '('); + jspGetLeftArg(v, &elem); + printJsonPathItem(buf, &elem, false, true); + printOperation(buf, v->type); + jspGetRightArg(v, &elem); + printJsonPathItem(buf, &elem, false, true); + appendStringInfoChar(buf, ')'); + break; + case jpiExpression: + appendBinaryStringInfo(buf, "?(", 2); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoChar(buf, ')'); + break; + case jpiEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + printOperation(buf, v->type); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, true); + break; + case jpiNot: + appendBinaryStringInfo(buf, "(! ", 2); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, true); + appendStringInfoChar(buf, ')'); + break; + case jpiCurrent: + Assert(!inKey); + appendStringInfoChar(buf, '@'); + break; + case jpiRoot: + Assert(!inKey); + appendStringInfoChar(buf, '$'); + break; + case jpiAnyArray: + appendBinaryStringInfo(buf, "[*]", 3); + break; + case jpiAnyKey: + if (inKey) + appendStringInfoChar(buf, '.'); + appendStringInfoChar(buf, '*'); + break; + default: + elog(ERROR, "Unknown jsonpath item type: %d", v->type); + } + + if (jspGetNext(v, &elem)) + printJsonPathItem(buf, &elem, true, true); +} + +Datum +jsonpath_out(PG_FUNCTION_ARGS) +{ + JsonPath *in = PG_GETARG_JSONPATH_P(0); + StringInfoData buf; + JsonPathItem v; + + initStringInfo(&buf); + enlargeStringInfo(&buf, VARSIZE(in) /* estimation */); + + jspInit(&v, in); + printJsonPathItem(&buf, &v, false, true); + + PG_RETURN_CSTRING(buf.data); +} + +/* + * Support functions for JsonPath + */ +#define read_byte(v, b, p) do { \ + (v) = *(uint8*)((b) + (p)); \ + (p) += 1; \ +} while(0) \ + +#define read_int32(v, b, p) do { \ + (v) = *(uint32*)((b) + (p)); \ + (p) += sizeof(int32); \ +} while(0) \ + +void +jspInit(JsonPathItem *v, JsonPath *js) +{ + jspInitByBuffer(v, VARDATA(js), 0); +} + +void +jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) +{ + v->base = base; + + read_byte(v->type, base, pos); + + switch(INTALIGN(pos) - pos) + { + case 3: pos++; + case 2: pos++; + case 1: pos++; + default: break; + } + + read_int32(v->nextPos, base, pos); + + switch(v->type) + { + case jpiNull: + case jpiRoot: + case jpiCurrent: + case jpiAnyArray: + case jpiAnyKey: + break; + case jpiKey: + case jpiString: + case jpiVariable: + read_int32(v->value.datalen, base, pos); + /* follow next */ + case jpiNumeric: + case jpiBool: + v->value.data = base + pos; + break; + case jpiAnd: + case jpiOr: + read_int32(v->args.left, base, pos); + read_int32(v->args.right, base, pos); + break; + case jpiEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiNot: + case jpiExpression: + read_int32(v->arg, base, pos); + break; + default: + elog(ERROR, "Unknown jsonpath item type: %d", v->type); + } +} + +void +jspGetArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert( + v->type == jpiEqual || + v->type == jpiLess || + v->type == jpiGreater || + v->type == jpiLessOrEqual || + v->type == jpiGreaterOrEqual || + v->type == jpiExpression || + v->type == jpiNot + ); + + jspInitByBuffer(a, v->base, v->arg); +} + +bool +jspGetNext(JsonPathItem *v, JsonPathItem *a) +{ + if (v->nextPos > 0) + { + Assert( + v->type == jpiKey || + v->type == jpiAnyArray || + v->type == jpiAnyKey || + v->type == jpiCurrent || + v->type == jpiRoot + ); + + if (a) + jspInitByBuffer(a, v->base, v->nextPos); + return true; + } + + return false; +} + +void +jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert( + v->type == jpiAnd || + v->type == jpiOr + ); + + jspInitByBuffer(a, v->base, v->args.left); +} + +void +jspGetRightArg(JsonPathItem *v, JsonPathItem *a) +{ + Assert( + v->type == jpiAnd || + v->type == jpiOr + ); + + jspInitByBuffer(a, v->base, v->args.right); +} + +bool +jspGetBool(JsonPathItem *v) +{ + Assert(v->type == jpiBool); + + return (bool)*v->value.data; +} + +Numeric +jspGetNumeric(JsonPathItem *v) +{ + Assert(v->type == jpiNumeric); + + return (Numeric)v->value.data; +} + +char* +jspGetString(JsonPathItem *v, int32 *len) +{ + Assert( + v->type == jpiKey || + v->type == jpiString || + v->type == jpiVariable + ); + + if (len) + *len = v->value.datalen; + return v->value.data; +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c new file mode 100644 index 0000000000..12c148916f --- /dev/null +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -0,0 +1,420 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_exec.c + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_exec.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "lib/stringinfo.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonpath.h" + +static int +compareNumeric(Numeric a, Numeric b) +{ + return DatumGetInt32( + DirectFunctionCall2( + numeric_cmp, + PointerGetDatum(a), + PointerGetDatum(b) + ) + ); +} + +#define jbvScalar jbvBinary +static int +JsonbType(JsonbValue *jb) +{ + int type = jb->type; + + if (jb->type == jbvBinary) + { + JsonbContainer *jbc = jb->val.binary.data; + + if (jbc->header & JB_FSCALAR) + type = jbvScalar; + else if (jbc->header & JB_FOBJECT) + type = jbvObject; + else if (jbc->header & JB_FARRAY) + type = jbvArray; + else + elog(ERROR, "Unknown container type: 0x%08x", jbc->header); + } + + return type; +} + +static bool +checkScalarEquality(JsonPathItem *jsp, JsonbValue *jb) +{ + int len; + char *s; + + if (jb->type == jbvBinary) + return false; + + if ((int)jb->type != (int)jsp->type /* see enums */) + return false; + + switch(jsp->type) + { + case jpiNull: + return true; + case jpiString: + s = jspGetString(jsp, &len); + return (len == jb->val.string.len && memcmp(jb->val.string.val, s, len) == 0); + case jpiBool: + return (jb->val.boolean == jspGetBool(jsp)); + case jpiNumeric: + return (compareNumeric(jspGetNumeric(jsp), jb->val.numeric) == 0); + default: + elog(ERROR,"Wrong state"); + } + + return false; +} + +static bool +makeCompare(JsonPathItem *jsp, int32 op, JsonbValue *jb) +{ + int res; + + if (jb->type != jbvNumeric) + return false; + if (jsp->type != jpiNumeric) + return false; + + res = compareNumeric(jb->val.numeric, jspGetNumeric(jsp)); + + switch(op) + { + case jpiEqual: + return (res == 0); + case jpiLess: + return (res < 0); + case jpiGreater: + return (res > 0); + case jpiLessOrEqual: + return (res <= 0); + case jpiGreaterOrEqual: + return (res >= 0); + default: + elog(ERROR, "Unknown operation"); + } + + return false; +} + +static bool +executeExpr(JsonPathItem *jsp, int32 op, JsonbValue *jb) +{ + bool res = false; + /* + * read arg type + */ + Assert(jspGetNext(jsp, NULL) == false); + Assert(jsp->type == jpiString || jsp->type == jpiNumeric || + jsp->type == jpiNull || jsp->type == jpiBool); + + switch(op) + { + case jpiEqual: + res = checkScalarEquality(jsp, jb); + break; + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + res = makeCompare(jsp, op, jb); + break; + default: + elog(ERROR, "Unknown operation"); + } + + return res; +} + +static JsonPathExecResult +recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) +{ + JsonPathItem elem; + JsonPathExecResult res = jperNotFound; + + check_stack_depth(); + + switch(jsp->type) { + case jpiAnd: + jspGetLeftArg(jsp, &elem); + res = recursiveExecute(&elem, jb, NULL); + if (res == jperOk) + { + jspGetRightArg(jsp, &elem); + res = recursiveExecute(&elem, jb, NULL); + } + break; + case jpiOr: + jspGetLeftArg(jsp, &elem); + res = recursiveExecute(&elem, jb, NULL); + if (res == jperNotFound) + { + jspGetRightArg(jsp, &elem); + res = recursiveExecute(&elem, jb, NULL); + } + break; + case jpiNot: + jspGetArg(jsp, &elem); + switch((res = recursiveExecute(&elem, jb, NULL))) + { + case jperOk: + res = jperNotFound; + break; + case jperNotFound: + res = jperOk; + break; + default: + break; + } + break; + case jpiKey: + if (JsonbType(jb) == jbvObject) + { + JsonbValue *v, key; + + key.type = jbvString; + key.val.string.val = jspGetString(jsp, &key.val.string.len); + + v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key); + + if (v != NULL) + { + if (jspGetNext(jsp, &elem)) + { + res = recursiveExecute(&elem, v, found); + pfree(v); + } + else + { + res = jperOk; + if (found) + *found = lappend(*found, v); + else + pfree(v); + } + } + } + break; + case jpiCurrent: + jspGetNext(jsp, &elem); + if (JsonbType(jb) == jbvScalar) + { + JsonbValue v; + + JsonbExtractScalar(jb->val.binary.data, &v); + + res = recursiveExecute(&elem, &v, NULL); + } + else + { + res = recursiveExecute(&elem, jb, NULL); + } + break; + case jpiAnyArray: + if (JsonbType(jb) == jbvArray) + { + JsonbIterator *it; + int32 r; + JsonbValue v, *pv; + bool hasNext; + + hasNext = jspGetNext(jsp, &elem); + it = JsonbIteratorInit(jb->val.binary.data); + + while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_ELEM) + { + if (hasNext == true) + { + res = recursiveExecute(&elem, &v, found); + + if (res == jperError || found == NULL) + break; + } + else + { + res = jperOk; + + if (found == NULL) + break; + + pv = palloc(sizeof(*pv)); + *pv = v; + *found = lappend(*found, pv); + } + } + } + } + break; + /* + case jpiIndexArray: + if (JsonbType(jb) == jbvArray) + { + JsonbValue *v; + + jspGetNext(jsp, &elem); + + v = getIthJsonbValueFromContainer(jb->val.binary.data, + jsp->arrayIndex); + + res = v && recursiveExecute(&elem, v, found); + } + break; + */ + case jpiAnyKey: + if (JsonbType(jb) == jbvObject) + { + JsonbIterator *it; + int32 r; + JsonbValue v, *pv; + bool hasNext; + + hasNext = jspGetNext(jsp, &elem); + it = JsonbIteratorInit(jb->val.binary.data); + + while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_VALUE) + { + if (hasNext == true) + { + res = recursiveExecute(&elem, &v, found); + + if (res == jperError || found == NULL) + break; + } + else + { + res = jperOk; + + if (found == NULL) + break; + + pv = palloc(sizeof(*pv)); + *pv = v; + *found = lappend(*found, pv); + } + } + } + } + break; + case jpiEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + jspGetArg(jsp, &elem); + res = executeExpr(&elem, jsp->type, jb); + break; + case jpiRoot: + /* no-op actually */ + jspGetNext(jsp, &elem); + res = recursiveExecute(&elem, jb, found); + break; + case jpiExpression: + /* no-op actually */ + jspGetNext(jsp, &elem); + res = recursiveExecute(&elem, jb, NULL); + break; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); + } + + return res; +} + +JsonPathExecResult +executeJsonPath(JsonPath *path, Jsonb *json, List **foundJson) +{ + JsonPathItem jsp; + JsonbValue jbv; + + jbv.type = jbvBinary; + jbv.val.binary.data = &json->root; + jbv.val.binary.len = VARSIZE_ANY_EXHDR(json); + + jspInit(&jsp, path); + + return recursiveExecute(&jsp, &jbv, foundJson); +} + +Datum +jsonb_jsonpath_exists(PG_FUNCTION_ARGS) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonPathExecResult res; + + res = executeJsonPath(jp, jb, NULL); + + PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(jp, 1); + + if (res == jperError) + elog(ERROR, "Something wrong"); + + PG_RETURN_BOOL(res == jperOk); +} + +Datum +jsonb_jsonpath_query(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + List *found = NIL; + JsonbValue *v; + ListCell *c; + + if (SRF_IS_FIRSTCALL()) + { + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + Jsonb *jb; + JsonPathExecResult res; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + jb = PG_GETARG_JSONB_P_COPY(0); + res = executeJsonPath(jp, jb, &found); + + if (res == jperError) + elog(ERROR, "Something wrong"); + + PG_FREE_IF_COPY(jp, 1); + + funcctx->user_fctx = found; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + found = funcctx->user_fctx; + + c = list_head(found); + + if (c == NULL) + SRF_RETURN_DONE(funcctx); + + v = lfirst(c); + funcctx->user_fctx = list_delete_first(found); + + SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v))); +} diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y new file mode 100644 index 0000000000..cd0033b75b --- /dev/null +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -0,0 +1,278 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_gram.y + * Grammar definitions for jsonpath datatype + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_gram.y + * + *------------------------------------------------------------------------- + */ + +%{ +#include "postgres.h" + +#include "fmgr.h" +#include "nodes/pg_list.h" +#include "utils/builtins.h" +#include "utils/jsonpath.h" + +#include "utils/jsonpath_scanner.h" + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. Note this only works with + * bison >= 2.0. However, in bison 1.875 the default is to use alloca() + * if possible, so there's not really much problem anyhow, at least if + * you're building with gcc. + */ +#define YYMALLOC palloc +#define YYFREE pfree + +static JsonPathParseItem* +makeItemType(int type) +{ + JsonPathParseItem* v = palloc(sizeof(*v)); + + v->type = type; + v->next = NULL; + + return v; +} + +static JsonPathParseItem* +makeItemString(string *s) +{ + JsonPathParseItem *v; + + if (s == NULL) + { + v = makeItemType(jpiNull); + } + else + { + v = makeItemType(jpiString); + v->string.val = s->val; + v->string.len = s->len; + } + + return v; +} + +static JsonPathParseItem* +makeItemVariable(string *s) +{ + JsonPathParseItem *v; + + v = makeItemType(jpiVariable); + v->string.val = s->val; + v->string.len = s->len; + + return v; +} + +static JsonPathParseItem* +makeItemKey(string *s) +{ + JsonPathParseItem *v; + + v = makeItemString(s); + v->type = jpiKey; + + return v; +} + +static JsonPathParseItem* +makeItemNumeric(string *s) +{ + JsonPathParseItem *v; + + v = makeItemType(jpiNumeric); + v->numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1)); + + return v; +} + +static JsonPathParseItem* +makeItemBool(bool val) { + JsonPathParseItem *v = makeItemType(jpiBool); + + v->boolean = val; + + return v; +} + +static JsonPathParseItem* +makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra) +{ + JsonPathParseItem *v = makeItemType(type); + + v->args.left = la; + v->args.right = ra; + + return v; +} + +static JsonPathParseItem* +makeItemUnary(int type, JsonPathParseItem* a) +{ + JsonPathParseItem *v = makeItemType(type); + + v->arg = a; + + return v; +} + +static JsonPathParseItem* +makeItemList(List *list) { + JsonPathParseItem *head, *end; + ListCell *cell; + + head = end = (JsonPathParseItem*)linitial(list); + + foreach(cell, list) + { + JsonPathParseItem *c = (JsonPathParseItem*)lfirst(cell); + + if (c == head) + continue; + + end->next = c; + end = c; + } + + return head; +} + +static JsonPathParseItem* +makeItemExpression(List *path, JsonPathParseItem *right_expr) +{ + JsonPathParseItem *expr = makeItemUnary(jpiExpression, right_expr); + + return makeItemList(lappend(path, expr)); +} + +%} + +/* BISON Declarations */ +%pure-parser +%expect 0 +%name-prefix="jsonpath_yy" +%error-verbose +%parse-param {JsonPathParseItem **result} + +%union { + string str; + List *elems; /* list of JsonPathParseItem */ + JsonPathParseItem *value; +} + +%token TO_P NULL_P TRUE_P FALSE_P +%token STRING_P NUMERIC_P INT_P + +%token OR_P AND_P NOT_P + +%type result scalar_value + +%type joined_key path absolute_path relative_path + +%type key any_key right_expr expr jsonpath numeric + +%left OR_P +%left AND_P +%right NOT_P +%nonassoc '(' ')' + +/* Grammar follows */ +%% + +result: + jsonpath { *result = $1; } + | /* EMPTY */ { *result = NULL; } + ; + +scalar_value: + STRING_P { $$ = makeItemString(&$1); } + | TO_P { $$ = makeItemString(&$1); } + | NULL_P { $$ = makeItemString(NULL); } + | TRUE_P { $$ = makeItemBool(true); } + | FALSE_P { $$ = makeItemBool(false); } + | NUMERIC_P { $$ = makeItemNumeric(&$1); } + | INT_P { $$ = makeItemNumeric(&$1); } + | '$' STRING_P { $$ = makeItemVariable(&$2); } + ; + +numeric: + NUMERIC_P { $$ = makeItemNumeric(&$1); } + | INT_P { $$ = makeItemNumeric(&$1); } + | '$' STRING_P { $$ = makeItemVariable(&$2); } + ; + +right_expr: + '=' scalar_value { $$ = makeItemUnary(jpiEqual, $2); } + | '<' numeric { $$ = makeItemUnary(jpiLess, $2); } + | '>' numeric { $$ = makeItemUnary(jpiGreater, $2); } + | '<' '=' numeric { $$ = makeItemUnary(jpiLessOrEqual, $3); } + | '>' '=' numeric { $$ = makeItemUnary(jpiGreaterOrEqual, $3); } + ; + +jsonpath: + absolute_path { $$ = makeItemList($1); } + | absolute_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } + | relative_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } + ; + +expr: + any_key right_expr { $$ = makeItemList(list_make2($1, $2)); } + | '.' any_key right_expr { $$ = makeItemList(list_make2($2, $3)); } + | '@' right_expr + { $$ = makeItemList(list_make2(makeItemType(jpiCurrent), $2)); } + | '@' '.' any_key right_expr + { $$ = makeItemList(list_make3(makeItemType(jpiCurrent),$3, $4)); } + | relative_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } + | '(' expr ')' { $$ = $2; } + | expr AND_P expr { $$ = makeItemBinary(jpiAnd, $1, $3); } + | expr OR_P expr { $$ = makeItemBinary(jpiOr, $1, $3); } + | NOT_P expr { $$ = makeItemUnary(jpiNot, $2); } + ; + +any_key: + key { $$ = $1; } + | '*' { $$ = makeItemType(jpiAnyKey); } + | '[' '*' ']' { $$ = makeItemType(jpiAnyArray); } + ; + +joined_key: + any_key { $$ = list_make1($1); } + | joined_key '[' '*' ']' { $$ = lappend($1, makeItemType(jpiAnyArray)); } + ; +key: + STRING_P { $$ = makeItemKey(&$1); } + | TO_P { $$ = makeItemKey(&$1); } + | NULL_P { $$ = makeItemKey(&$1); } + | TRUE_P { $$ = makeItemKey(&$1); } + | FALSE_P { $$ = makeItemKey(&$1); } + ; + +absolute_path: + '$' '.' { $$ = list_make1(makeItemType(jpiRoot)); } + | '$' { $$ = list_make1(makeItemType(jpiRoot)); } + | '$' '.' path { $$ = lcons(makeItemType(jpiRoot), $3); } + ; + +relative_path: + joined_key '.' joined_key { $$ = list_concat($1, $3); } + | '.' joined_key '.' joined_key { $$ = list_concat($2, $4); } + | '@' '.' joined_key '.' joined_key { $$ = list_concat($3, $5); } + | relative_path '.' joined_key { $$ = list_concat($1, $3); } + +path: + joined_key { $$ = $1; } + | path '.' joined_key { $$ = list_concat($1, $3); } + ; + +%% + diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l new file mode 100644 index 0000000000..d2bbe5dedc --- /dev/null +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -0,0 +1,513 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_scan.l + * Lexical parser for jsonpath datatype + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/utils/adt/jsonpath_scan.l + * + *------------------------------------------------------------------------- + */ + +%{ +#include "postgres.h" +#include "mb/pg_wchar.h" +#include "nodes/pg_list.h" +#include "utils/jsonpath_scanner.h" + +static string scanstring; + +/* No reason to constrain amount of data slurped */ +/* #define YY_READ_BUF_SIZE 16777216 */ + +/* Handles to the buffer that the lexer uses internally */ +static YY_BUFFER_STATE scanbufhandle; +static char *scanbuf; +static int scanbuflen; + +static void addstring(bool init, char *s, int l); +static void addchar(bool init, char s); +static int checkSpecialVal(void); /* examine scanstring for the special value */ + +static void parseUnicode(char *s, int l); + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +#define yyerror jsonpath_yyerror +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option warn +%option prefix="jsonpath_yy" +%option bison-bridge +%option noyyalloc +%option noyyrealloc +%option noyyfree + +%x xQUOTED +%x xNONQUOTED +%x xCOMMENT + +special [\?\%\$\.\[\]\(\)\|\&\=\<\>\@\#\,\*:] +any [^\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\* \t\n\r\f\\\"\/:] +blank [ \t\n\r\f] +unicode \\u[0-9A-Fa-f]{4} + +%% + +\&\& { return AND_P; } + +\|\| { return OR_P; } + +\! { return NOT_P; } + +{special} { return *yytext; } + +{blank}+ { /* ignore */ } + +\/\* { + addchar(true, '\0'); + BEGIN xCOMMENT; + } + +[+-]?[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + +[+-]?\.[0-9]+[eE][+-]?[0-9]+ /* float */ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + +[+-]?([0-9]+)?\.[0-9]+ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + +[+-][0-9]+ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return NUMERIC_P; + } + +[0-9]+ { + addstring(true, yytext, yyleng); + addchar(false, '\0'); + yylval->str = scanstring; + return INT_P; + } + +{any}+ { + addstring(true, yytext, yyleng); + BEGIN xNONQUOTED; + } + +\" { + addchar(true, '\0'); + BEGIN xQUOTED; + } + +\\ { + yyless(0); + addchar(true, '\0'); + BEGIN xNONQUOTED; + } + +{any}+ { + addstring(false, yytext, yyleng); + } + +{blank}+ { + yylval->str = scanstring; + BEGIN INITIAL; + return checkSpecialVal(); + } + + +\/\* { + yylval->str = scanstring; + BEGIN xCOMMENT; + return checkSpecialVal(); + } + +({special}|\") { + yylval->str = scanstring; + yyless(0); + BEGIN INITIAL; + return checkSpecialVal(); + } + +<> { + yylval->str = scanstring; + BEGIN INITIAL; + return checkSpecialVal(); + } + +\\[\"\\] { addchar(false, yytext[1]); } + +\\b { addchar(false, '\b'); } + +\\f { addchar(false, '\f'); } + +\\n { addchar(false, '\n'); } + +\\r { addchar(false, '\r'); } + +\\t { addchar(false, '\t'); } + +{unicode}+ { parseUnicode(yytext, yyleng); } + +\\u { yyerror(NULL, "Unicode sequence is invalid"); } + +\\. { yyerror(NULL, "Escape sequence is invalid"); } + +\\ { yyerror(NULL, "Unexpected end after backslesh"); } + +<> { yyerror(NULL, "Unexpected end of quoted string"); } + +\" { + yylval->str = scanstring; + BEGIN INITIAL; + return STRING_P; + } + +[^\\\"]+ { addstring(false, yytext, yyleng); } + +<> { yyterminate(); } + +\*\/ { BEGIN INITIAL; } + +[^\*]+ { } + +\* { } + +<> { yyerror(NULL, "Unexpected end of comment"); } + +%% + +void +jsonpath_yyerror(JsonPathParseItem **result, const char *message) +{ + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad jsonpath representation"), + /* translator: %s is typically "syntax error" */ + errdetail("%s at end of input", message))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad jsonpath representation"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s at or near \"%s\"", message, yytext))); + } +} + +typedef struct keyword +{ + int16 len; + bool lowercase; + int val; + char *keyword; +} keyword; + +/* + * Array of key words should be sorted by length and then + * alphabetical order + */ + +static keyword keywords[] = { + { 2, false, TO_P, "to"}, + { 4, true, NULL_P, "null"}, + { 4, true, TRUE_P, "true"}, + { 5, true, FALSE_P, "false"} +}; + +static int +checkSpecialVal() +{ + int res = STRING_P; + int diff; + keyword *StopLow = keywords, + *StopHigh = keywords + lengthof(keywords), + *StopMiddle; + + if (scanstring.len > keywords[lengthof(keywords) - 1].len) + return res; + + while(StopLow < StopHigh) + { + StopMiddle = StopLow + ((StopHigh - StopLow) >> 1); + + if (StopMiddle->len == scanstring.len) + diff = pg_strncasecmp(StopMiddle->keyword, scanstring.val, scanstring.len); + else + diff = StopMiddle->len - scanstring.len; + + if (diff < 0) + StopLow = StopMiddle + 1; + else if (diff > 0) + StopHigh = StopMiddle; + else + { + if (StopMiddle->lowercase) + diff = strncmp(StopMiddle->keyword, scanstring.val, scanstring.len); + + if (diff == 0) + res = StopMiddle->val; + + break; + } + } + + return res; +} + +/* + * Called before any actual parsing is done + */ +static void +jsonpath_scanner_init(const char *str, int slen) +{ + if (slen <= 0) + slen = strlen(str); + + /* + * Might be left over after ereport() + */ + yy_init_globals(); + + /* + * Make a scan buffer with special termination needed by flex. + */ + + scanbuflen = slen; + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after jsonpath_scanner_init() + */ +static void +jsonpath_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} + +static void +addstring(bool init, char *s, int l) { + if (init) { + scanstring.total = 32; + scanstring.val = palloc(scanstring.total); + scanstring.len = 0; + } + + if (s && l) { + while(scanstring.len + l + 1 >= scanstring.total) { + scanstring.total *= 2; + scanstring.val = repalloc(scanstring.val, scanstring.total); + } + + memcpy(scanstring.val + scanstring.len, s, l); + scanstring.len += l; + } +} + +static void +addchar(bool init, char s) { + if (init) + { + scanstring.total = 32; + scanstring.val = palloc(scanstring.total); + scanstring.len = 0; + } + else if(scanstring.len + 1 >= scanstring.total) + { + scanstring.total *= 2; + scanstring.val = repalloc(scanstring.val, scanstring.total); + } + + scanstring.val[ scanstring.len ] = s; + if (s != '\0') + scanstring.len++; +} + +JsonPathParseItem* +parsejsonpath(const char *str, int len) { + JsonPathParseItem *parseresult; + + jsonpath_scanner_init(str, len); + + if (jsonpath_yyparse((void*)&parseresult) != 0) + jsonpath_yyerror(NULL, "bugus input"); + + jsonpath_scanner_finish(); + + return parseresult; +} + +static int +hexval(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 0xA; + if (c >= 'A' && c <= 'F') + return c - 'A' + 0xA; + elog(ERROR, "invalid hexadecimal digit"); + return 0; /* not reached */ +} + +/* + * parseUnicode was adopted from json_lex_string() in + * src/backend/utils/adt/json.c + */ +static void +parseUnicode(char *s, int l) +{ + int i, j; + int ch = 0; + int hi_surrogate = -1; + + Assert(l % 6 /* \uXXXX */ == 0); + + for(i = 0; i < l / 6; i++) + { + ch = 0; + + for(j=0; j<4; j++) + ch = (ch << 4) | hexval(s[ i*6 + 2 + j]); + + if (ch >= 0xd800 && ch <= 0xdbff) + { + if (hi_surrogate != -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode high surrogate must not follow a high surrogate."))); + hi_surrogate = (ch & 0x3ff) << 10; + continue; + } + else if (ch >= 0xdc00 && ch <= 0xdfff) + { + if (hi_surrogate == -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode low surrogate must follow a high surrogate."))); + ch = 0x10000 + hi_surrogate + (ch & 0x3ff); + hi_surrogate = -1; + } + + if (hi_surrogate != -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode low surrogate must follow a high surrogate."))); + + /* + * For UTF8, replace the escape sequence by the actual + * utf8 character in lex->strval. Do this also for other + * encodings if the escape designates an ASCII character, + * otherwise raise an error. + */ + + if (ch == 0) + { + /* We can't allow this, since our TEXT type doesn't */ + ereport(ERROR, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("unsupported Unicode escape sequence"), + errdetail("\\u0000 cannot be converted to text."))); + } + else if (GetDatabaseEncoding() == PG_UTF8) + { + char utf8str[5]; + int utf8len; + + unicode_to_utf8(ch, (unsigned char *) utf8str); + utf8len = pg_utf_mblen((unsigned char *) utf8str); + addstring(false, utf8str, utf8len); + } + else if (ch <= 0x007f) + { + /* + * This is the only way to designate things like a + * form feed character in JSON, so it's useful in all + * encodings. + */ + addchar(false, (char) ch); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8."))); + } + + hi_surrogate = -1; + } +} + +/* + * Interface functions to make flex use palloc() instead of malloc(). + * It'd be better to make these static, but flex insists otherwise. + */ + +void * +jsonpath_yyalloc(yy_size_t bytes) +{ + return palloc(bytes); +} + +void * +jsonpath_yyrealloc(void *ptr, yy_size_t bytes) +{ + if (ptr) + return repalloc(ptr, bytes); + else + return palloc(bytes); +} + +void +jsonpath_yyfree(void *ptr) +{ + if (ptr) + pfree(ptr); +} + diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 66c6c224a8..27998773e7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9296,6 +9296,21 @@ proname => 'jsonb_insert', prorettype => 'jsonb', proargtypes => 'jsonb _text jsonb bool', prosrc => 'jsonb_insert' }, +# jsonpath +{ oid => '6052', descr => 'I/O', + proname => 'jsonpath_in', prorettype => 'jsonpath', proargtypes => 'cstring', + prosrc => 'jsonpath_in' }, +{ oid => '6053', descr => 'I/O', + proname => 'jsonpath_out', prorettype => 'cstring', proargtypes => 'jsonpath', + prosrc => 'jsonpath_out' }, +{ oid => '6054', descr => 'jsonpath exists test', + proname => '_jsonpath_exists', prorettype => 'bool', + proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists' }, +{ oid => '6055', descr => 'jsonpath query', + proname => '_jsonpath_query', prorows => '1000', proretset => 't', + prorettype => 'jsonb', proargtypes => 'jsonb jsonpath', + prosrc => 'jsonb_jsonpath_query' }, + # 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 48e01cd694..8f2fc29b3b 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -770,6 +770,16 @@ typelem => 'jsonb', typinput => 'array_in', typoutput => 'array_out', typreceive => 'array_recv', typsend => 'array_send', typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' }, +{ oid => '6050', + typname => 'jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'U', + typarray => '_jsonpath', typinput => 'jsonpath_in', + typoutput => 'jsonpath_out', typreceive => '-', typsend => '-', + typalign => 'i', typstorage => 'x' } +{ oid => '6051', + typname => '_jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'A', + typelem => 'jsonpath', typinput => 'array_in', typoutput => 'array_out', + typreceive => 'array_recv', typsend => 'array_send', + typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' }, { oid => '2970', descr => 'txid snapshot', typname => 'txid_snapshot', typlen => '-1', typbyval => 'f', diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h index 8551237fc6..ff1ecb20ef 100644 --- a/src/include/lib/stringinfo.h +++ b/src/include/lib/stringinfo.h @@ -157,4 +157,10 @@ extern void appendBinaryStringInfoNT(StringInfo str, */ extern void enlargeStringInfo(StringInfo str, int needed); +/*------------------------ + * alignStringInfoInt + * Add padding zero bytes to align StringInfo + */ +extern void alignStringInfoInt(StringInfo buf); + #endif /* STRINGINFO_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 27873d4d10..602490a35a 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -66,8 +66,10 @@ typedef enum /* Convenience macros */ #define DatumGetJsonbP(d) ((Jsonb *) PG_DETOAST_DATUM(d)) +#define DatumGetJsonbPCopy(d) ((Jsonb *) PG_DETOAST_DATUM_COPY(d)) #define JsonbPGetDatum(p) PointerGetDatum(p) #define PG_GETARG_JSONB_P(x) DatumGetJsonbP(PG_GETARG_DATUM(x)) +#define PG_GETARG_JSONB_P_COPY(x) DatumGetJsonbPCopy(PG_GETARG_DATUM(x)) #define PG_RETURN_JSONB_P(x) PG_RETURN_POINTER(x) typedef struct JsonbPair JsonbPair; @@ -379,5 +381,6 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern JsonbValue *JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res); #endif /* __JSONB_H__ */ diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h new file mode 100644 index 0000000000..855ef99df4 --- /dev/null +++ b/src/include/utils/jsonpath.h @@ -0,0 +1,156 @@ +/*------------------------------------------------------------------------- + * + * jsonpath.h + * Definitions of jsonpath datatype + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/utils/jsonpath.h + * + *------------------------------------------------------------------------- + */ + +#ifndef JSONPATH_H +#define JSONPATH_H + +#include "fmgr.h" +#include "utils/jsonb.h" +#include "nodes/pg_list.h" + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ +} JsonPath; + +#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)) +#define PG_GETARG_JSONPATH_P_COPY(x) DatumGetJsonPathPCopy(PG_GETARG_DATUM(x)) +#define PG_RETURN_JSONPATH_P(p) PG_RETURN_POINTER(p) + +typedef enum JsonPathItemType { + jpiNull = jbvNull, + jpiString = jbvString, + jpiNumeric = jbvNumeric, + jpiBool = jbvBool, + jpiAnd, + jpiOr, + jpiNot, + jpiEqual, + jpiLess, + jpiGreater, + jpiLessOrEqual, + jpiGreaterOrEqual, + jpiAnyArray, + jpiAnyKey, + //jpiAny, + //jpiAll, + //jpiAllArray, + //jpiAllKey, + jpiKey, + jpiCurrent, + jpiRoot, + jpiVariable, + jpiExpression +} JsonPathItemType; + + +/* + * Support functions to parse/construct binary value. + * Unlike many other representation of expression the first/main + * node is not an operation but left operand of expression. That + * allows to implement cheep follow-path descending in jsonb + * structure and then execute operator with right operand which + * is always a constant. + */ + +typedef struct JsonPathItem { + JsonPathItemType type; + int32 nextPos; + char *base; + + union { + struct { + char *data; /* for bool, numeric and string/key */ + int datalen; /* filled only for string/key */ + } value; + + struct { + int32 left; + int32 right; + } args; + + int32 arg; + + struct { + int nelems; + int current; + int32 *arrayPtr; + } array; + + uint32 arrayIndex; + }; +} JsonPathItem; + +extern void jspInit(JsonPathItem *v, JsonPath *js); +extern void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos); +extern bool jspGetNext(JsonPathItem *v, JsonPathItem *a); +extern void jspGetArg(JsonPathItem *v, JsonPathItem *a); +extern void jspGetLeftArg(JsonPathItem *v, JsonPathItem *a); +extern void jspGetRightArg(JsonPathItem *v, JsonPathItem *a); +extern Numeric jspGetNumeric(JsonPathItem *v); +extern bool jspGetBool(JsonPathItem *v); +extern char * jspGetString(JsonPathItem *v, int32 *len); + +/* + * Parsing + */ + +typedef struct JsonPathParseItem JsonPathParseItem; + +struct JsonPathParseItem { + JsonPathItemType type; + JsonPathParseItem *next; /* next in path */ + + union { + struct { + JsonPathParseItem *left; + JsonPathParseItem *right; + } args; + + JsonPathParseItem *arg; + int8 isType; /* jbv* values */ + + Numeric numeric; + bool boolean; + struct { + uint32 len; + char *val; /* could not be not null-terminated */ + } string; + + struct { + int nelems; + JsonPathParseItem **elems; + } array; + + uint32 arrayIndex; + }; +}; + +extern JsonPathParseItem* parsejsonpath(const char *str, int len); + +/* + * Execution + */ + +typedef enum JsonPathExecResult { + jperOk = 0, + jperError, + jperFatalError, + jperNotFound +} JsonPathExecResult; + +JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *json, + List **foundJson); +#endif diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h new file mode 100644 index 0000000000..c7be6bea88 --- /dev/null +++ b/src/include/utils/jsonpath_scanner.h @@ -0,0 +1,30 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_scanner.h + * jsonpath scanner & parser support + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * + * src/include/utils/jsonpath_scanner.h + * + *------------------------------------------------------------------------- + */ + +#ifndef JSONPATH_SCANNER_H +#define JSONPATH_SCANNER_H + +/* struct string is shared between scan and gram */ +typedef struct string { + char *val; + int len; + int total; +} string; + +#include "utils/jsonpath.h" +#include "utils/jsonpath_gram.h" + +/* flex 2.5.4 doesn't bother with a decl for this */ +extern int jsonpath_yylex(YYSTYPE * yylval_param); +extern void jsonpath_yyerror(JsonPathParseItem **result, const char *message); + +#endif diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out new file mode 100644 index 0000000000..404bb1cfdd --- /dev/null +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -0,0 +1,92 @@ +select _jsonpath_exists(jsonb '{"a": 12}', '$.a.b'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"a": 12}', '$.b'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.a.a'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{}', '$.*'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[]', '$.[*]'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '[1]', '$.[*]'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); + _jsonpath_query +----------------- + 12 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); + _jsonpath_query +----------------- + {"a": 13} +(1 row) + +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); + _jsonpath_query +----------------- + 12 + {"a": 13} +(2 rows) + +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*.a'); + _jsonpath_query +----------------- + 13 +(1 row) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].a'); + _jsonpath_query +----------------- + 13 +(1 row) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); + _jsonpath_query +----------------- + 13 + 14 +(2 rows) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out new file mode 100644 index 0000000000..91e6926baa --- /dev/null +++ b/src/test/regress/expected/jsonpath.out @@ -0,0 +1,425 @@ +--jsonpath io +select ''::jsonpath; +ERROR: invalid input syntax for jsonpath: "" +LINE 1: select ''::jsonpath; + ^ +select '$'::jsonpath; + jsonpath +---------- + $ +(1 row) + +select '$.a'::jsonpath; + jsonpath +---------- + $."a" +(1 row) + +select '$.a.v'::jsonpath; + jsonpath +----------- + $."a"."v" +(1 row) + +select '$.a.*'::jsonpath; + jsonpath +---------- + $."a".* +(1 row) + +select '$.*.[*]'::jsonpath; + jsonpath +---------- + $.*[*] +(1 row) + +select '$.*[*]'::jsonpath; + jsonpath +---------- + $.*[*] +(1 row) + +select '$.a.[*]'::jsonpath; + jsonpath +---------- + $."a"[*] +(1 row) + +select '$.a[*]'::jsonpath; + jsonpath +---------- + $."a"[*] +(1 row) + +select '$.a.[*][*]'::jsonpath; + jsonpath +------------- + $."a"[*][*] +(1 row) + +select '$.a.[*].[*]'::jsonpath; + jsonpath +------------- + $."a"[*][*] +(1 row) + +select '$.a[*][*]'::jsonpath; + jsonpath +------------- + $."a"[*][*] +(1 row) + +select '$.a[*].[*]'::jsonpath; + jsonpath +------------- + $."a"[*][*] +(1 row) + +select '$.g ? (@ = 1)'::jsonpath; + jsonpath +--------------- + $."g"?(@ = 1) +(1 row) + +select '$.g ? (a = 1)'::jsonpath; + jsonpath +----------------- + $."g"?("a" = 1) +(1 row) + +select '$.g ? (.a = 1)'::jsonpath; + jsonpath +----------------- + $."g"?("a" = 1) +(1 row) + +select '$.g ? (@.a = 1)'::jsonpath; + jsonpath +------------------- + $."g"?(@."a" = 1) +(1 row) + +select '$.g ? (@.a = 1 || a = 4)'::jsonpath; + jsonpath +-------------------------------- + $."g"?((@."a" = 1 || "a" = 4)) +(1 row) + +select '$.g ? (@.a = 1 && a = 4)'::jsonpath; + jsonpath +-------------------------------- + $."g"?((@."a" = 1 && "a" = 4)) +(1 row) + +select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; + jsonpath +--------------------------------------------- + $."g"?((@."a" = 1 || ("a" = 4 && "b" = 7))) +(1 row) + +select '$.g ? (@.a = 1 || !a = 4 && b = 7)'::jsonpath; + jsonpath +------------------------------------------------ + $."g"?((@."a" = 1 || ((!"a" = 4) && "b" = 7))) +(1 row) + +select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; + jsonpath +---------------------------------------------------------------- + $."g"?((@."a" = 1 || ((!("x" >= 123 || "a" = 4)) && "b" = 7))) +(1 row) + +select '$.g ? (zip = $zip)'::jsonpath; + jsonpath +------------------------ + $."g"?("zip" = $"zip") +(1 row) + +select '$ ? (a < 1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < -1)'::jsonpath; + jsonpath +-------------- + $?("a" < -1) +(1 row) + +select '$ ? (a < +1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < .1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < -.1)'::jsonpath; + jsonpath +---------------- + $?("a" < -0.1) +(1 row) + +select '$ ? (a < +.1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < 0.1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < -0.1)'::jsonpath; + jsonpath +---------------- + $?("a" < -0.1) +(1 row) + +select '$ ? (a < +0.1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < 10.1)'::jsonpath; + jsonpath +---------------- + $?("a" < 10.1) +(1 row) + +select '$ ? (a < -10.1)'::jsonpath; + jsonpath +----------------- + $?("a" < -10.1) +(1 row) + +select '$ ? (a < +10.1)'::jsonpath; + jsonpath +---------------- + $?("a" < 10.1) +(1 row) + +select '$ ? (a < 1e1)'::jsonpath; + jsonpath +-------------- + $?("a" < 10) +(1 row) + +select '$ ? (a < -1e1)'::jsonpath; + jsonpath +--------------- + $?("a" < -10) +(1 row) + +select '$ ? (a < +1e1)'::jsonpath; + jsonpath +-------------- + $?("a" < 10) +(1 row) + +select '$ ? (a < .1e1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < -.1e1)'::jsonpath; + jsonpath +-------------- + $?("a" < -1) +(1 row) + +select '$ ? (a < +.1e1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < 0.1e1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < -0.1e1)'::jsonpath; + jsonpath +-------------- + $?("a" < -1) +(1 row) + +select '$ ? (a < +0.1e1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < 10.1e1)'::jsonpath; + jsonpath +--------------- + $?("a" < 101) +(1 row) + +select '$ ? (a < -10.1e1)'::jsonpath; + jsonpath +---------------- + $?("a" < -101) +(1 row) + +select '$ ? (a < +10.1e1)'::jsonpath; + jsonpath +--------------- + $?("a" < 101) +(1 row) + +select '$ ? (a < 1e-1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < -1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < -0.1) +(1 row) + +select '$ ? (a < +1e-1)'::jsonpath; + jsonpath +--------------- + $?("a" < 0.1) +(1 row) + +select '$ ? (a < .1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 0.01) +(1 row) + +select '$ ? (a < -.1e-1)'::jsonpath; + jsonpath +----------------- + $?("a" < -0.01) +(1 row) + +select '$ ? (a < +.1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 0.01) +(1 row) + +select '$ ? (a < 0.1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 0.01) +(1 row) + +select '$ ? (a < -0.1e-1)'::jsonpath; + jsonpath +----------------- + $?("a" < -0.01) +(1 row) + +select '$ ? (a < +0.1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 0.01) +(1 row) + +select '$ ? (a < 10.1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 1.01) +(1 row) + +select '$ ? (a < -10.1e-1)'::jsonpath; + jsonpath +----------------- + $?("a" < -1.01) +(1 row) + +select '$ ? (a < +10.1e-1)'::jsonpath; + jsonpath +---------------- + $?("a" < 1.01) +(1 row) + +select '$ ? (a < 1e+1)'::jsonpath; + jsonpath +-------------- + $?("a" < 10) +(1 row) + +select '$ ? (a < -1e+1)'::jsonpath; + jsonpath +--------------- + $?("a" < -10) +(1 row) + +select '$ ? (a < +1e+1)'::jsonpath; + jsonpath +-------------- + $?("a" < 10) +(1 row) + +select '$ ? (a < .1e+1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < -.1e+1)'::jsonpath; + jsonpath +-------------- + $?("a" < -1) +(1 row) + +select '$ ? (a < +.1e+1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < 0.1e+1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < -0.1e+1)'::jsonpath; + jsonpath +-------------- + $?("a" < -1) +(1 row) + +select '$ ? (a < +0.1e+1)'::jsonpath; + jsonpath +------------- + $?("a" < 1) +(1 row) + +select '$ ? (a < 10.1e+1)'::jsonpath; + jsonpath +--------------- + $?("a" < 101) +(1 row) + +select '$ ? (a < -10.1e+1)'::jsonpath; + jsonpath +---------------- + $?("a" < -101) +(1 row) + +select '$ ? (a < +10.1e+1)'::jsonpath; + jsonpath +--------------- + $?("a" < 101) +(1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 16f979c8d9..afa8c35e4f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -104,7 +104,12 @@ test: publication subscription # ---------- # Another group of parallel tests # ---------- -test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass +test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass + +# ---------- +# Another group of parallel tests (JSON related) +# ---------- +test: json jsonb json_encoding jsonpath jsonb_jsonpath # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 42632be675..bcb46a6126 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -160,6 +160,8 @@ test: advisory_lock test: json test: jsonb test: json_encoding +test: jsonpath +test: jsonb_jsonpath test: indirect_toast test: equivclass test: plancache diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql new file mode 100644 index 0000000000..2e940eae0e --- /dev/null +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -0,0 +1,16 @@ +select _jsonpath_exists(jsonb '{"a": 12}', '$.a.b'); +select _jsonpath_exists(jsonb '{"a": 12}', '$.b'); +select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.a.a'); +select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); +select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); +select _jsonpath_exists(jsonb '{}', '$.*'); +select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); +select _jsonpath_exists(jsonb '[]', '$.[*]'); +select _jsonpath_exists(jsonb '[1]', '$.[*]'); + +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*.a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql new file mode 100644 index 0000000000..f626a7c5a1 --- /dev/null +++ b/src/test/regress/sql/jsonpath.sql @@ -0,0 +1,76 @@ +--jsonpath io + +select ''::jsonpath; +select '$'::jsonpath; +select '$.a'::jsonpath; +select '$.a.v'::jsonpath; +select '$.a.*'::jsonpath; +select '$.*.[*]'::jsonpath; +select '$.*[*]'::jsonpath; +select '$.a.[*]'::jsonpath; +select '$.a[*]'::jsonpath; +select '$.a.[*][*]'::jsonpath; +select '$.a.[*].[*]'::jsonpath; +select '$.a[*][*]'::jsonpath; +select '$.a[*].[*]'::jsonpath; + +select '$.g ? (@ = 1)'::jsonpath; +select '$.g ? (a = 1)'::jsonpath; +select '$.g ? (.a = 1)'::jsonpath; +select '$.g ? (@.a = 1)'::jsonpath; +select '$.g ? (@.a = 1 || a = 4)'::jsonpath; +select '$.g ? (@.a = 1 && a = 4)'::jsonpath; +select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; +select '$.g ? (@.a = 1 || !a = 4 && b = 7)'::jsonpath; +select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; + +select '$.g ? (zip = $zip)'::jsonpath; + +select '$ ? (a < 1)'::jsonpath; +select '$ ? (a < -1)'::jsonpath; +select '$ ? (a < +1)'::jsonpath; +select '$ ? (a < .1)'::jsonpath; +select '$ ? (a < -.1)'::jsonpath; +select '$ ? (a < +.1)'::jsonpath; +select '$ ? (a < 0.1)'::jsonpath; +select '$ ? (a < -0.1)'::jsonpath; +select '$ ? (a < +0.1)'::jsonpath; +select '$ ? (a < 10.1)'::jsonpath; +select '$ ? (a < -10.1)'::jsonpath; +select '$ ? (a < +10.1)'::jsonpath; +select '$ ? (a < 1e1)'::jsonpath; +select '$ ? (a < -1e1)'::jsonpath; +select '$ ? (a < +1e1)'::jsonpath; +select '$ ? (a < .1e1)'::jsonpath; +select '$ ? (a < -.1e1)'::jsonpath; +select '$ ? (a < +.1e1)'::jsonpath; +select '$ ? (a < 0.1e1)'::jsonpath; +select '$ ? (a < -0.1e1)'::jsonpath; +select '$ ? (a < +0.1e1)'::jsonpath; +select '$ ? (a < 10.1e1)'::jsonpath; +select '$ ? (a < -10.1e1)'::jsonpath; +select '$ ? (a < +10.1e1)'::jsonpath; +select '$ ? (a < 1e-1)'::jsonpath; +select '$ ? (a < -1e-1)'::jsonpath; +select '$ ? (a < +1e-1)'::jsonpath; +select '$ ? (a < .1e-1)'::jsonpath; +select '$ ? (a < -.1e-1)'::jsonpath; +select '$ ? (a < +.1e-1)'::jsonpath; +select '$ ? (a < 0.1e-1)'::jsonpath; +select '$ ? (a < -0.1e-1)'::jsonpath; +select '$ ? (a < +0.1e-1)'::jsonpath; +select '$ ? (a < 10.1e-1)'::jsonpath; +select '$ ? (a < -10.1e-1)'::jsonpath; +select '$ ? (a < +10.1e-1)'::jsonpath; +select '$ ? (a < 1e+1)'::jsonpath; +select '$ ? (a < -1e+1)'::jsonpath; +select '$ ? (a < +1e+1)'::jsonpath; +select '$ ? (a < .1e+1)'::jsonpath; +select '$ ? (a < -.1e+1)'::jsonpath; +select '$ ? (a < +.1e+1)'::jsonpath; +select '$ ? (a < 0.1e+1)'::jsonpath; +select '$ ? (a < -0.1e+1)'::jsonpath; +select '$ ? (a < +0.1e+1)'::jsonpath; +select '$ ? (a < 10.1e+1)'::jsonpath; +select '$ ? (a < -10.1e+1)'::jsonpath; +select '$ ? (a < +10.1e+1)'::jsonpath; From 9b79ed216e2a91aef2426dc332d95be7da837d9f Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 16:26:51 +0300 Subject: [PATCH 06/97] add array indexes to jsonpath --- src/backend/utils/adt/jsonpath.c | 45 +++++++++++--- src/backend/utils/adt/jsonpath_exec.c | 48 ++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 62 ++++++++++++++++---- src/include/utils/jsonpath.h | 11 +--- src/test/regress/expected/jsonb_jsonpath.out | 40 +++++++++++++ src/test/regress/expected/jsonpath.out | 12 ++++ src/test/regress/sql/jsonb_jsonpath.sql | 7 +++ src/test/regress/sql/jsonpath.sql | 2 + 8 files changed, 193 insertions(+), 34 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 8ac882c7e0..d947b966c2 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -86,6 +86,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) case jpiRoot: case jpiNull: break; + case jpiIndexArray: + appendBinaryStringInfo(buf, + (char*)&item->array.nelems, + sizeof(item->array.nelems)); + appendBinaryStringInfo(buf, + (char*)item->array.elems, + item->array.nelems * sizeof(item->array.elems[0])); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -151,6 +159,7 @@ static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes) { JsonPathItem elem; + int i; check_stack_depth(); @@ -229,6 +238,16 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket appendStringInfoChar(buf, '.'); appendStringInfoChar(buf, '*'); break; + case jpiIndexArray: + appendStringInfoChar(buf, '['); + for(i = 0; i< v->array.nelems; i++) + { + if (i) + appendStringInfoChar(buf, ','); + appendStringInfo(buf, "%d", v->array.elems[i]); + } + appendStringInfoChar(buf, ']'); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); } @@ -256,15 +275,20 @@ jsonpath_out(PG_FUNCTION_ARGS) /* * Support functions for JsonPath */ -#define read_byte(v, b, p) do { \ - (v) = *(uint8*)((b) + (p)); \ - (p) += 1; \ -} while(0) \ +#define read_byte(v, b, p) do { \ + (v) = *(uint8*)((b) + (p)); \ + (p) += 1; \ +} while(0) \ + +#define read_int32(v, b, p) do { \ + (v) = *(uint32*)((b) + (p)); \ + (p) += sizeof(int32); \ +} while(0) \ -#define read_int32(v, b, p) do { \ - (v) = *(uint32*)((b) + (p)); \ - (p) += sizeof(int32); \ -} while(0) \ +#define read_int32_n(v, b, p, n) do { \ + (v) = (int32*)((b) + (p)); \ + (p) += sizeof(int32) * (n); \ +} while(0) \ void jspInit(JsonPathItem *v, JsonPath *js) @@ -320,6 +344,10 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiExpression: read_int32(v->arg, base, pos); break; + case jpiIndexArray: + read_int32(v->array.nelems, base, pos); + read_int32_n(v->array.elems, base, pos, v->array.nelems); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); } @@ -350,6 +378,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiKey || v->type == jpiAnyArray || v->type == jpiAnyKey || + v->type == jpiIndexArray || v->type == jpiCurrent || v->type == jpiRoot ); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 12c148916f..b187599473 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -246,7 +246,10 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { res = recursiveExecute(&elem, &v, found); - if (res == jperError || found == NULL) + if (res == jperError) + break; + + if (res == jperOk && found == NULL) break; } else @@ -264,21 +267,47 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) } } break; - /* + case jpiIndexArray: if (JsonbType(jb) == jbvArray) { JsonbValue *v; + bool hasNext; + int i; + + hasNext = jspGetNext(jsp, &elem); + + for(i=0; iarray.nelems; i++) + { + /* TODO for future: array index can be expression */ + v = getIthJsonbValueFromContainer(jb->val.binary.data, + jsp->array.elems[i]); + + if (v == NULL) + continue; + + if (hasNext == true) + { + res = recursiveExecute(&elem, v, found); - jspGetNext(jsp, &elem); + if (res == jperError || found == NULL) + break; - v = getIthJsonbValueFromContainer(jb->val.binary.data, - jsp->arrayIndex); + if (res == jperOk && found == NULL) + break; + } + else + { + res = jperOk; - res = v && recursiveExecute(&elem, v, found); + if (found == NULL) + break; + + *found = lappend(*found, v); + } + } } break; - */ case jpiAnyKey: if (JsonbType(jb) == jbvObject) { @@ -298,7 +327,10 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { res = recursiveExecute(&elem, &v, found); - if (res == jperError || found == NULL) + if (res == jperError) + break; + + if (res == jperOk && found == NULL) break; } else diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index cd0033b75b..1973b07829 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -1,12 +1,12 @@ /*------------------------------------------------------------------------- * * jsonpath_gram.y - * Grammar definitions for jsonpath datatype + * Grammar definitions for jsonpath datatype * * Copyright (c) 2017, PostgreSQL Global Development Group * * IDENTIFICATION - * src/backend/utils/adt/jsonpath_gram.y + * src/backend/utils/adt/jsonpath_gram.y * *------------------------------------------------------------------------- */ @@ -155,6 +155,24 @@ makeItemExpression(List *path, JsonPathParseItem *right_expr) return makeItemList(lappend(path, expr)); } +static JsonPathParseItem* +makeIndexArray(List *list) +{ + JsonPathParseItem *v = makeItemType(jpiIndexArray); + ListCell *cell; + int i = 0; + + Assert(list_length(list) > 0); + v->array.nelems = list_length(list); + + v->array.elems = palloc(sizeof(v->array.elems[0]) * v->array.nelems); + + foreach(cell, list) + v->array.elems[i++] = lfirst_int(cell); + + return v; +} + %} /* BISON Declarations */ @@ -167,21 +185,24 @@ makeItemExpression(List *path, JsonPathParseItem *right_expr) %union { string str; List *elems; /* list of JsonPathParseItem */ + List *indexs; JsonPathParseItem *value; } -%token TO_P NULL_P TRUE_P FALSE_P +%token TO_P NULL_P TRUE_P FALSE_P %token STRING_P NUMERIC_P INT_P %token OR_P AND_P NOT_P -%type result scalar_value +%type result scalar_value %type joined_key path absolute_path relative_path %type key any_key right_expr expr jsonpath numeric -%left OR_P +%type index_elem index_list + +%left OR_P %left AND_P %right NOT_P %nonassoc '(' ')' @@ -189,8 +210,8 @@ makeItemExpression(List *path, JsonPathParseItem *right_expr) /* Grammar follows */ %% -result: - jsonpath { *result = $1; } +result: + jsonpath { *result = $1; } | /* EMPTY */ { *result = NULL; } ; @@ -206,9 +227,9 @@ scalar_value: ; numeric: - NUMERIC_P { $$ = makeItemNumeric(&$1); } - | INT_P { $$ = makeItemNumeric(&$1); } - | '$' STRING_P { $$ = makeItemVariable(&$2); } + NUMERIC_P { $$ = makeItemNumeric(&$1); } + | INT_P { $$ = makeItemNumeric(&$1); } + | '$' STRING_P { $$ = makeItemVariable(&$2); } ; right_expr: @@ -239,15 +260,36 @@ expr: | NOT_P expr { $$ = makeItemUnary(jpiNot, $2); } ; +index_elem: + INT_P { $$ = list_make1_int(pg_atoi($1.val, 4, 0)); } + | INT_P TO_P INT_P { + int start = pg_atoi($1.val, 4, 0), + stop = pg_atoi($3.val, 4, 0), + i; + + $$ = NIL; + + for(i=start; i<= stop; i++) + $$ = lappend_int($$, i); + } + ; + +index_list: + index_elem { $$ = $1; } + | index_list ',' index_elem { $$ = list_concat($1, $3); } + ; + any_key: key { $$ = $1; } | '*' { $$ = makeItemType(jpiAnyKey); } | '[' '*' ']' { $$ = makeItemType(jpiAnyArray); } + | '[' index_list ']' { $$ = makeIndexArray($2); } ; joined_key: any_key { $$ = list_make1($1); } | joined_key '[' '*' ']' { $$ = lappend($1, makeItemType(jpiAnyArray)); } + | joined_key '[' index_list ']' { $$ = lappend($1, makeIndexArray($3)); } ; key: STRING_P { $$ = makeItemKey(&$1); } diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 855ef99df4..9ecae5c425 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -44,6 +44,7 @@ typedef enum JsonPathItemType { jpiGreaterOrEqual, jpiAnyArray, jpiAnyKey, + jpiIndexArray, //jpiAny, //jpiAll, //jpiAllArray, @@ -85,11 +86,8 @@ typedef struct JsonPathItem { struct { int nelems; - int current; - int32 *arrayPtr; + int32 *elems; } array; - - uint32 arrayIndex; }; } JsonPathItem; @@ -120,7 +118,6 @@ struct JsonPathParseItem { } args; JsonPathParseItem *arg; - int8 isType; /* jbv* values */ Numeric numeric; bool boolean; @@ -131,10 +128,8 @@ struct JsonPathParseItem { struct { int nelems; - JsonPathParseItem **elems; + int32 *elems; } array; - - uint32 arrayIndex; }; }; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 404bb1cfdd..b150adbd8b 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -52,6 +52,18 @@ select _jsonpath_exists(jsonb '[1]', '$.[*]'); t (1 row) +select _jsonpath_exists(jsonb '[1]', '$.[1]'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '[1]', '$.[0]'); + _jsonpath_exists +------------------ + t +(1 row) + select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); _jsonpath_query ----------------- @@ -90,3 +102,31 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); 14 (2 rows) +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0].a'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[1].a'); + _jsonpath_query +----------------- + 13 +(1 row) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[2].a'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); + _jsonpath_query +----------------- + 13 +(1 row) + +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); + _jsonpath_query +----------------- + 13 +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 91e6926baa..63e48515e3 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -135,6 +135,18 @@ select '$.g ? (zip = $zip)'::jsonpath; $."g"?("zip" = $"zip") (1 row) +select '$.a.[1,2, 3 to 16]'::jsonpath; + jsonpath +----------------------------------------------- + $."a"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] +(1 row) + +select '$.a[1,2, 3 to 16]'::jsonpath; + jsonpath +----------------------------------------------- + $."a"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] +(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 2e940eae0e..ee5f2ade7b 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -7,6 +7,8 @@ select _jsonpath_exists(jsonb '{}', '$.*'); select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); select _jsonpath_exists(jsonb '[]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[*]'); +select _jsonpath_exists(jsonb '[1]', '$.[1]'); +select _jsonpath_exists(jsonb '[1]', '$.[0]'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); @@ -14,3 +16,8 @@ select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*.a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[2].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index f626a7c5a1..45d7119501 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -25,6 +25,8 @@ select '$.g ? (@.a = 1 || !a = 4 && b = 7)'::jsonpath; select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; select '$.g ? (zip = $zip)'::jsonpath; +select '$.a.[1,2, 3 to 16]'::jsonpath; +select '$.a[1,2, 3 to 16]'::jsonpath; select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; From 4b9806fd09a75d1a93f611b7d6d8ea12c12c3ae4 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 19:20:27 +0300 Subject: [PATCH 07/97] Add passing params to jsonpath --- src/backend/utils/adt/jsonpath.c | 6 +- src/backend/utils/adt/jsonpath_exec.c | 379 ++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 7 +- src/backend/utils/adt/jsonpath_scan.l | 42 +- src/include/catalog/pg_proc.dat | 11 +- src/include/utils/jsonpath.h | 17 +- src/test/regress/expected/jsonb_jsonpath.out | 60 +++ src/test/regress/sql/jsonb_jsonpath.sql | 11 + 8 files changed, 448 insertions(+), 85 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d947b966c2..cca4001b4d 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -18,6 +18,7 @@ #include "utils/json.h" #include "utils/jsonpath.h" +/*****************************INPUT/OUTPUT************************************/ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) { @@ -272,9 +273,8 @@ jsonpath_out(PG_FUNCTION_ARGS) PG_RETURN_CSTRING(buf.data); } -/* - * Support functions for JsonPath - */ +/********************Support functions for JsonPath****************************/ + #define read_byte(v, b, p) do { \ (v) = *(uint8*)((b) + (p)); \ (p) += 1; \ diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index b187599473..6b9ed6bb3c 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -13,23 +13,133 @@ #include "postgres.h" #include "funcapi.h" #include "miscadmin.h" +#include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/jsonpath.h" -static int -compareNumeric(Numeric a, Numeric b) +/********************Execute functions for JsonPath***************************/ + +static void +computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) { - return DatumGetInt32( - DirectFunctionCall2( - numeric_cmp, - PointerGetDatum(a), - PointerGetDatum(b) - ) - ); + ListCell *cell; + JsonPathVariable *var = NULL; + bool isNull; + Datum computedValue; + char *varName; + int varNameLength; + + Assert(variable->type == jpiVariable); + varName = jspGetString(variable, &varNameLength); + + foreach(cell, vars) + { + var = (JsonPathVariable*)lfirst(cell); + + if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) && + !strncmp(varName, VARDATA_ANY(var->varName), varNameLength)) + break; + + var = NULL; + } + + if (var == NULL) + ereport(ERROR, + (errcode(ERRCODE_NO_DATA_FOUND), + errmsg("could not find '%s' passed variable", + pnstrdup(varName, varNameLength)))); + + computedValue = var->cb(var->cb_arg, &isNull); + + if (isNull) + { + value->type = jbvNull; + return; + } + + switch(var->typid) + { + case BOOLOID: + value->type = jbvBool; + value->val.boolean = DatumGetBool(computedValue); + break; + case NUMERICOID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(computedValue); + break; + break; + case INT2OID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall1( + int2_numeric, computedValue)); + break; + case INT4OID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall1( + int4_numeric, computedValue)); + break; + case INT8OID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall1( + int8_numeric, computedValue)); + break; + case FLOAT4OID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall1( + float4_numeric, computedValue)); + break; + case FLOAT8OID: + value->type = jbvNumeric; + value->val.numeric = DatumGetNumeric(DirectFunctionCall1( + float4_numeric, computedValue)); + break; + case TEXTOID: + case VARCHAROID: + value->type = jbvString; + value->val.string.val = VARDATA_ANY(computedValue); + value->val.string.len = VARSIZE_ANY_EXHDR(computedValue); + break; + case (Oid) -1: /* raw JsonbValue */ + *value = *(JsonbValue *) DatumGetPointer(computedValue); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("only bool, numeric and text types could be casted to supported jsonpath types"))); + } +} + +static void +computeJsonPathItem(JsonPathItem *item, List *vars, JsonbValue *value) +{ + switch(item->type) + { + case jpiNull: + value->type = jbvNull; + break; + case jpiBool: + value->type = jbvBool; + value->val.boolean = jspGetBool(item); + break; + case jpiNumeric: + value->type = jbvNumeric; + value->val.numeric = jspGetNumeric(item); + break; + case jpiString: + value->type = jbvString; + value->val.string.val = jspGetString(item, &value->val.string.len); + break; + case jpiVariable: + computeJsonPathVariable(item, vars, value); + break; + default: + elog(ERROR, "Wrong type"); + } } + #define jbvScalar jbvBinary static int JsonbType(JsonbValue *jb) @@ -53,29 +163,42 @@ JsonbType(JsonbValue *jb) return type; } +static int +compareNumeric(Numeric a, Numeric b) +{ + return DatumGetInt32( + DirectFunctionCall2( + numeric_cmp, + PointerGetDatum(a), + PointerGetDatum(b) + ) + ); +} static bool -checkScalarEquality(JsonPathItem *jsp, JsonbValue *jb) +checkScalarEquality(JsonPathItem *jsp, List *vars, JsonbValue *jb) { - int len; - char *s; + JsonbValue computedValue; if (jb->type == jbvBinary) return false; - if ((int)jb->type != (int)jsp->type /* see enums */) + computeJsonPathItem(jsp, vars, &computedValue); + + if (jb->type != computedValue.type) return false; - switch(jsp->type) + switch(computedValue.type) { - case jpiNull: + case jbvNull: return true; - case jpiString: - s = jspGetString(jsp, &len); - return (len == jb->val.string.len && memcmp(jb->val.string.val, s, len) == 0); - case jpiBool: - return (jb->val.boolean == jspGetBool(jsp)); - case jpiNumeric: - return (compareNumeric(jspGetNumeric(jsp), jb->val.numeric) == 0); + case jbvString: + return (computedValue.val.string.len == jb->val.string.len && + memcmp(jb->val.string.val, computedValue.val.string.val, + computedValue.val.string.len) == 0); + case jbvBool: + return (jb->val.boolean == computedValue.val.boolean); + case jbvNumeric: + return (compareNumeric(computedValue.val.numeric, jb->val.numeric) == 0); default: elog(ERROR,"Wrong state"); } @@ -84,16 +207,20 @@ checkScalarEquality(JsonPathItem *jsp, JsonbValue *jb) } static bool -makeCompare(JsonPathItem *jsp, int32 op, JsonbValue *jb) +makeCompare(JsonPathItem *jsp, List *vars, int32 op, JsonbValue *jb) { - int res; + int res; + JsonbValue computedValue; if (jb->type != jbvNumeric) return false; - if (jsp->type != jpiNumeric) + + computeJsonPathItem(jsp, vars, &computedValue); + + if (computedValue.type != jbvNumeric) return false; - res = compareNumeric(jb->val.numeric, jspGetNumeric(jsp)); + res = compareNumeric(jb->val.numeric, computedValue.val.numeric); switch(op) { @@ -114,27 +241,28 @@ makeCompare(JsonPathItem *jsp, int32 op, JsonbValue *jb) return false; } -static bool -executeExpr(JsonPathItem *jsp, int32 op, JsonbValue *jb) +static JsonPathExecResult +executeExpr(JsonPathItem *jsp, List *vars, int32 op, JsonbValue *jb) { - bool res = false; + JsonPathExecResult res = jperNotFound; /* * read arg type */ Assert(jspGetNext(jsp, NULL) == false); Assert(jsp->type == jpiString || jsp->type == jpiNumeric || - jsp->type == jpiNull || jsp->type == jpiBool); + jsp->type == jpiNull || jsp->type == jpiBool || + jsp->type == jpiVariable); switch(op) { case jpiEqual: - res = checkScalarEquality(jsp, jb); + res = checkScalarEquality(jsp, vars, jb) ? jperOk : jperNotFound; break; case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: - res = makeCompare(jsp, op, jb); + res = makeCompare(jsp, vars, op, jb) ? jperOk : jperNotFound; break; default: elog(ERROR, "Unknown operation"); @@ -143,8 +271,18 @@ executeExpr(JsonPathItem *jsp, int32 op, JsonbValue *jb) return res; } +static JsonbValue* +copyJsonbValue(JsonbValue *src) +{ + JsonbValue *dst = palloc(sizeof(*dst)); + + *dst = *src; + + return dst; +} + static JsonPathExecResult -recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) +recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -154,25 +292,25 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) switch(jsp->type) { case jpiAnd: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(&elem, jb, NULL); + res = recursiveExecute(&elem, vars, jb, NULL); if (res == jperOk) { jspGetRightArg(jsp, &elem); - res = recursiveExecute(&elem, jb, NULL); + res = recursiveExecute(&elem, vars, jb, NULL); } break; case jpiOr: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(&elem, jb, NULL); + res = recursiveExecute(&elem, vars, jb, NULL); if (res == jperNotFound) { jspGetRightArg(jsp, &elem); - res = recursiveExecute(&elem, jb, NULL); + res = recursiveExecute(&elem, vars, jb, NULL); } break; case jpiNot: jspGetArg(jsp, &elem); - switch((res = recursiveExecute(&elem, jb, NULL))) + switch((res = recursiveExecute(&elem, vars, jb, NULL))) { case jperOk: res = jperNotFound; @@ -198,7 +336,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { if (jspGetNext(jsp, &elem)) { - res = recursiveExecute(&elem, v, found); + res = recursiveExecute(&elem, vars, v, found); pfree(v); } else @@ -220,11 +358,11 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) JsonbExtractScalar(jb->val.binary.data, &v); - res = recursiveExecute(&elem, &v, NULL); + res = recursiveExecute(&elem, vars, &v, NULL); } else { - res = recursiveExecute(&elem, jb, NULL); + res = recursiveExecute(&elem, vars, jb, NULL); } break; case jpiAnyArray: @@ -232,7 +370,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonbIterator *it; int32 r; - JsonbValue v, *pv; + JsonbValue v; bool hasNext; hasNext = jspGetNext(jsp, &elem); @@ -244,7 +382,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { if (hasNext == true) { - res = recursiveExecute(&elem, &v, found); + res = recursiveExecute(&elem, vars, &v, found); if (res == jperError) break; @@ -259,9 +397,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) if (found == NULL) break; - pv = palloc(sizeof(*pv)); - *pv = v; - *found = lappend(*found, pv); + *found = lappend(*found, copyJsonbValue(&v)); } } } @@ -288,7 +424,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) if (hasNext == true) { - res = recursiveExecute(&elem, v, found); + res = recursiveExecute(&elem, vars, v, found); if (res == jperError || found == NULL) break; @@ -313,7 +449,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonbIterator *it; int32 r; - JsonbValue v, *pv; + JsonbValue v; bool hasNext; hasNext = jspGetNext(jsp, &elem); @@ -325,7 +461,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { if (hasNext == true) { - res = recursiveExecute(&elem, &v, found); + res = recursiveExecute(&elem, vars, &v, found); if (res == jperError) break; @@ -340,9 +476,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) if (found == NULL) break; - pv = palloc(sizeof(*pv)); - *pv = v; - *found = lappend(*found, pv); + *found = lappend(*found, copyJsonbValue(&v)); } } } @@ -354,17 +488,27 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) case jpiLessOrEqual: case jpiGreaterOrEqual: jspGetArg(jsp, &elem); - res = executeExpr(&elem, jsp->type, jb); + res = executeExpr(&elem, vars, jsp->type, jb); break; case jpiRoot: - /* no-op actually */ - jspGetNext(jsp, &elem); - res = recursiveExecute(&elem, jb, found); + if (jspGetNext(jsp, &elem)) + { + res = recursiveExecute(&elem, vars, jb, found); + } + else + { + res = jperOk; + if (found) + *found = lappend(*found, copyJsonbValue(jb)); + } + break; case jpiExpression: /* no-op actually */ - jspGetNext(jsp, &elem); - res = recursiveExecute(&elem, jb, NULL); + jspGetArg(jsp, &elem); + res = recursiveExecute(&elem, vars, jb, NULL); + if (res == jperOk && found) + *found = lappend(*found, copyJsonbValue(jb)); break; default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); @@ -374,7 +518,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) } JsonPathExecResult -executeJsonPath(JsonPath *path, Jsonb *json, List **foundJson) +executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) { JsonPathItem jsp; JsonbValue jbv; @@ -385,17 +529,98 @@ executeJsonPath(JsonPath *path, Jsonb *json, List **foundJson) jspInit(&jsp, path); - return recursiveExecute(&jsp, &jbv, foundJson); + return recursiveExecute(&jsp, vars, &jbv, foundJson); } -Datum +static Datum +returnDATUM(void *arg, bool *isNull) +{ + *isNull = false; + return PointerGetDatum(arg); +} + +static Datum +returnNULL(void *arg, bool *isNull) +{ + *isNull = true; + return Int32GetDatum(0); +} + +static List* +makePassingVars(Jsonb *jb) +{ + JsonbValue v; + JsonbIterator *it; + int32 r; + List *vars = NIL; + + it = JsonbIteratorInit(&jb->root); + + r = JsonbIteratorNext(&it, &v, true); + + if (r != WJB_BEGIN_OBJECT) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("passing variable json is not a object"))); + + while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_KEY) + { + JsonPathVariable *jpv = palloc0(sizeof(*jpv)); + + jpv->varName = cstring_to_text_with_len(v.val.string.val, + v.val.string.len); + + JsonbIteratorNext(&it, &v, true); + + jpv->cb = returnDATUM; + + switch(v.type) + { + case jbvBool: + jpv->typid = BOOLOID; + jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean)); + break; + case jbvNull: + jpv->cb = returnNULL; + break; + case jbvString: + jpv->typid = TEXTOID; + jpv->cb_arg = cstring_to_text_with_len(v.val.string.val, + v.val.string.len); + break; + case jbvNumeric: + jpv->typid = NUMERICOID; + jpv->cb_arg = v.val.numeric; + break; + case jbvBinary: + jpv->typid = JSONBOID; + jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v))); + break; + default: + elog(ERROR, "unsupported type in passing variable json"); + } + + vars = lappend(vars, jpv); + } + } + + return vars; +} + +static Datum jsonb_jsonpath_exists(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); JsonPath *jp = PG_GETARG_JSONPATH_P(1); JsonPathExecResult res; + List *vars = NIL; + + if (PG_NARGS() == 3) + vars = makePassingVars(PG_GETARG_JSONB_P(2)); - res = executeJsonPath(jp, jb, NULL); + res = executeJsonPath(jp, vars, jb, NULL); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); @@ -407,6 +632,18 @@ jsonb_jsonpath_exists(PG_FUNCTION_ARGS) } Datum +jsonb_jsonpath_exists2(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_exists(fcinfo); +} + +Datum +jsonb_jsonpath_exists3(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_exists(fcinfo); +} + +static Datum jsonb_jsonpath_query(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; @@ -420,12 +657,16 @@ jsonb_jsonpath_query(PG_FUNCTION_ARGS) Jsonb *jb; JsonPathExecResult res; MemoryContext oldcontext; + List *vars = NIL; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); jb = PG_GETARG_JSONB_P_COPY(0); - res = executeJsonPath(jp, jb, &found); + if (PG_NARGS() == 3) + vars = makePassingVars(PG_GETARG_JSONB_P(2)); + + res = executeJsonPath(jp, vars, jb, &found); if (res == jperError) elog(ERROR, "Something wrong"); @@ -450,3 +691,15 @@ jsonb_jsonpath_query(PG_FUNCTION_ARGS) SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v))); } + +Datum +jsonb_jsonpath_query2(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_query(fcinfo); +} + +Datum +jsonb_jsonpath_query3(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_query(fcinfo); +} diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 1973b07829..7ae97facbe 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -190,8 +190,7 @@ makeIndexArray(List *list) } %token TO_P NULL_P TRUE_P FALSE_P -%token STRING_P NUMERIC_P INT_P - +%token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %type result scalar_value @@ -223,13 +222,13 @@ scalar_value: | FALSE_P { $$ = makeItemBool(false); } | NUMERIC_P { $$ = makeItemNumeric(&$1); } | INT_P { $$ = makeItemNumeric(&$1); } - | '$' STRING_P { $$ = makeItemVariable(&$2); } + | VARIABLE_P { $$ = makeItemVariable(&$1); } ; numeric: NUMERIC_P { $$ = makeItemNumeric(&$1); } | INT_P { $$ = makeItemNumeric(&$1); } - | '$' STRING_P { $$ = makeItemVariable(&$2); } + | VARIABLE_P { $$ = makeItemVariable(&$1); } ; right_expr: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index d2bbe5dedc..533d11158a 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -61,6 +61,7 @@ fprintf_to_ereport(const char *fmt, const char *msg) %x xQUOTED %x xNONQUOTED +%x xVARQUOTED %x xCOMMENT special [\?\%\$\.\[\]\(\)\|\&\=\<\>\@\#\,\*:] @@ -76,6 +77,18 @@ unicode \\u[0-9A-Fa-f]{4} \! { return NOT_P; } +\${any}+ { + addstring(true, yytext + 1, yyleng - 1); + addchar(false, '\0'); + yylval->str = scanstring; + return VARIABLE_P; + } + +\$\" { + addchar(true, '\0'); + BEGIN xVARQUOTED; + } + {special} { return *yytext; } {blank}+ { /* ignore */ } @@ -166,35 +179,40 @@ unicode \\u[0-9A-Fa-f]{4} return checkSpecialVal(); } -\\[\"\\] { addchar(false, yytext[1]); } +\\[\"\\] { addchar(false, yytext[1]); } -\\b { addchar(false, '\b'); } +\\b { addchar(false, '\b'); } -\\f { addchar(false, '\f'); } +\\f { addchar(false, '\f'); } -\\n { addchar(false, '\n'); } +\\n { addchar(false, '\n'); } -\\r { addchar(false, '\r'); } +\\r { addchar(false, '\r'); } -\\t { addchar(false, '\t'); } +\\t { addchar(false, '\t'); } -{unicode}+ { parseUnicode(yytext, yyleng); } +{unicode}+ { parseUnicode(yytext, yyleng); } -\\u { yyerror(NULL, "Unicode sequence is invalid"); } +\\u { yyerror(NULL, "Unicode sequence is invalid"); } -\\. { yyerror(NULL, "Escape sequence is invalid"); } +\\. { yyerror(NULL, "Escape sequence is invalid"); } -\\ { yyerror(NULL, "Unexpected end after backslesh"); } +\\ { yyerror(NULL, "Unexpected end after backslash"); } -<> { yyerror(NULL, "Unexpected end of quoted string"); } +<> { yyerror(NULL, "Unexpected end of quoted string"); } \" { yylval->str = scanstring; BEGIN INITIAL; return STRING_P; } +\" { + yylval->str = scanstring; + BEGIN INITIAL; + return VARIABLE_P; + } -[^\\\"]+ { addstring(false, yytext, yyleng); } +[^\\\"]+ { addstring(false, yytext, yyleng); } <> { yyterminate(); } diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 27998773e7..1245f6ea17 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9305,11 +9305,18 @@ prosrc => 'jsonpath_out' }, { oid => '6054', descr => 'jsonpath exists test', proname => '_jsonpath_exists', prorettype => 'bool', - proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists' }, + proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists2' }, { oid => '6055', descr => 'jsonpath query', proname => '_jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath', - prosrc => 'jsonb_jsonpath_query' }, + prosrc => 'jsonb_jsonpath_query2' }, +{ oid => '6056', descr => 'jsonpath exists test', + proname => '_jsonpath_exists', prorettype => 'bool', + proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_exists3' }, +{ oid => '6057', descr => 'jsonpath query', + proname => '_jsonpath_query', prorows => '1000', proretset => 't', + prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb', + prosrc => 'jsonb_jsonpath_query3' }, # txid { oid => '2939', descr => 'I/O', diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 9ecae5c425..f9d4efac62 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -146,6 +146,21 @@ typedef enum JsonPathExecResult { jperNotFound } JsonPathExecResult; -JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *json, +typedef Datum (*JsonPathVariable_cb)(void *, bool *); + +typedef struct JsonPathVariable { + text *varName; + Oid typid; + int32 typmod; /* do we need it here? */ + JsonPathVariable_cb cb; + void *cb_arg; +} JsonPathVariable; + + + +JsonPathExecResult executeJsonPath(JsonPath *path, + List *vars, /* list of JsonPathVariable */ + Jsonb *json, List **foundJson); + #endif diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index b150adbd8b..440b1b4d82 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -130,3 +130,63 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a 13 (1 row) +select * from _jsonpath_query(jsonb '{"a": 10}', '$'); + _jsonpath_query +----------------- + {"a": 10} +(1 row) + +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)'); +ERROR: could not find 'value' passed variable +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 13}'); + _jsonpath_query +----------------- + {"a": 10} +(1 row) + +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 8}'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); + _jsonpath_query +----------------- + 10 +(1 row) + +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); + _jsonpath_query +----------------- + 10 + 11 + 12 +(3 rows) + +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); + _jsonpath_query +----------------- + 10 + 11 +(2 rows) + +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); + _jsonpath_query +----------------- + 10 + 11 + 12 +(3 rows) + +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); + _jsonpath_query +----------------- + "1" +(1 row) + +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); + _jsonpath_query +----------------- + "1" +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index ee5f2ade7b..1ab808ab10 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -21,3 +21,14 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[1].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[2].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); + +select * from _jsonpath_query(jsonb '{"a": 10}', '$'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 8}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); From d130378b31f56943d6d7aa3cf11f450226de2b64 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 25 Feb 2017 12:47:48 +0300 Subject: [PATCH 08/97] Fix jsonpath grammar: add support for $[*] --- src/backend/utils/adt/jsonpath_gram.y | 26 ++++++++++++++++------ src/test/regress/expected/jsonpath.out | 30 ++++++++++++++++++++++++++ src/test/regress/sql/jsonpath.sql | 5 +++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 7ae97facbe..51e5b77ec1 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -195,9 +195,9 @@ makeIndexArray(List *list) %type result scalar_value -%type joined_key path absolute_path relative_path +%type joined_key path absolute_path relative_path array_accessors -%type key any_key right_expr expr jsonpath numeric +%type key any_key right_expr expr jsonpath numeric array_accessor %type index_elem index_list @@ -278,18 +278,27 @@ index_list: | index_list ',' index_elem { $$ = list_concat($1, $3); } ; +array_accessor: + '[' '*' ']' { $$ = makeItemType(jpiAnyArray); } + | '[' index_list ']' { $$ = makeIndexArray($2); } + ; + +array_accessors: + array_accessor { $$ = list_make1($1); } + | array_accessors array_accessor { $$ = lappend($1, $2); } + ; + any_key: key { $$ = $1; } + | array_accessor { $$ = $1; } | '*' { $$ = makeItemType(jpiAnyKey); } - | '[' '*' ']' { $$ = makeItemType(jpiAnyArray); } - | '[' index_list ']' { $$ = makeIndexArray($2); } ; joined_key: any_key { $$ = list_make1($1); } - | joined_key '[' '*' ']' { $$ = lappend($1, makeItemType(jpiAnyArray)); } - | joined_key '[' index_list ']' { $$ = lappend($1, makeIndexArray($3)); } + | joined_key array_accessor { $$ = lappend($1, $2); } ; + key: STRING_P { $$ = makeItemKey(&$1); } | TO_P { $$ = makeItemKey(&$1); } @@ -300,8 +309,10 @@ key: absolute_path: '$' '.' { $$ = list_make1(makeItemType(jpiRoot)); } - | '$' { $$ = list_make1(makeItemType(jpiRoot)); } + | '$' { $$ = list_make1(makeItemType(jpiRoot)); } | '$' '.' path { $$ = lcons(makeItemType(jpiRoot), $3); } + | '$' array_accessors { $$ = lcons(makeItemType(jpiRoot), $2); } + | '$' array_accessors '.' path { $$ = lcons(makeItemType(jpiRoot), list_concat($2, $4)); } ; relative_path: @@ -309,6 +320,7 @@ relative_path: | '.' joined_key '.' joined_key { $$ = list_concat($2, $4); } | '@' '.' joined_key '.' joined_key { $$ = list_concat($3, $5); } | relative_path '.' joined_key { $$ = list_concat($1, $3); } + ; path: joined_key { $$ = $1; } diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 63e48515e3..0761cc7835 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -75,6 +75,36 @@ select '$.a[*].[*]'::jsonpath; $."a"[*][*] (1 row) +select '$[*]'::jsonpath; + jsonpath +---------- + $[*] +(1 row) + +select '$[0]'::jsonpath; + jsonpath +---------- + $[0] +(1 row) + +select '$[*][0]'::jsonpath; + jsonpath +---------- + $[*][0] +(1 row) + +select '$[*].a'::jsonpath; + jsonpath +---------- + $[*]."a" +(1 row) + +select '$[*][0].a.b'::jsonpath; + jsonpath +----------------- + $[*][0]."a"."b" +(1 row) + select '$.g ? (@ = 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 45d7119501..75d31d78ef 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -13,6 +13,11 @@ select '$.a.[*][*]'::jsonpath; select '$.a.[*].[*]'::jsonpath; select '$.a[*][*]'::jsonpath; select '$.a[*].[*]'::jsonpath; +select '$[*]'::jsonpath; +select '$[0]'::jsonpath; +select '$[*][0]'::jsonpath; +select '$[*].a'::jsonpath; +select '$[*][0].a.b'::jsonpath; select '$.g ? (@ = 1)'::jsonpath; select '$.g ? (a = 1)'::jsonpath; From 6242e1201d1a163d55a4e59cdd5a457f1a2a1f21 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 25 Feb 2017 23:55:32 +0300 Subject: [PATCH 09/97] Add jsonb-typed variables support to jsonpath --- src/backend/utils/adt/jsonpath_exec.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 6b9ed6bb3c..67e044b802 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -101,6 +101,20 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) value->val.string.val = VARDATA_ANY(computedValue); value->val.string.len = VARSIZE_ANY_EXHDR(computedValue); break; + case JSONBOID: + { + Jsonb *jb = DatumGetJsonbP(computedValue); + + if (JB_ROOT_IS_SCALAR(jb)) + JsonbExtractScalar(&jb->root, value); + else + { + value->type = jbvBinary; + value->val.binary.data = &jb->root; + value->val.binary.len = VARSIZE_ANY_EXHDR(jb); + } + } + break; case (Oid) -1: /* raw JsonbValue */ *value = *(JsonbValue *) DatumGetPointer(computedValue); break; From 64dcb595c34206aed37b5f6319d45578ff825919 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 12:59:27 +0300 Subject: [PATCH 10/97] fix list of special characters in jsonpath --- src/backend/utils/adt/jsonpath_scan.l | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 533d11158a..0baaf95a5e 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -64,8 +64,8 @@ fprintf_to_ereport(const char *fmt, const char *msg) %x xVARQUOTED %x xCOMMENT -special [\?\%\$\.\[\]\(\)\|\&\=\<\>\@\#\,\*:] -any [^\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\* \t\n\r\f\\\"\/:] +special [\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/] +any [^\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f] blank [ \t\n\r\f] unicode \\u[0-9A-Fa-f]{4} From 4d3c88ff07cf8c7e902fc8aa5c14fcf12ca3e370 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 15:55:51 +0300 Subject: [PATCH 11/97] implement **{} path opt in jsonpath --- src/backend/utils/adt/jsonpath.c | 30 +++ src/backend/utils/adt/jsonpath_exec.c | 90 ++++++++ src/backend/utils/adt/jsonpath_gram.y | 24 ++- src/backend/utils/adt/jsonpath_scan.l | 4 +- src/include/utils/jsonpath.h | 24 ++- src/test/regress/expected/jsonb_jsonpath.out | 209 +++++++++++++++++++ src/test/regress/expected/jsonpath.out | 36 ++++ src/test/regress/sql/jsonb_jsonpath.sql | 38 ++++ src/test/regress/sql/jsonpath.sql | 6 + 9 files changed, 449 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index cca4001b4d..6c864090ec 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -95,6 +95,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) (char*)item->array.elems, item->array.nelems * sizeof(item->array.elems[0])); break; + case jpiAny: + appendBinaryStringInfo(buf, + (char*)&item->anybounds.first, + sizeof(item->anybounds.first)); + appendBinaryStringInfo(buf, + (char*)&item->anybounds.last, + sizeof(item->anybounds.last)); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -249,6 +257,23 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket } appendStringInfoChar(buf, ']'); break; + case jpiAny: + if (inKey) + appendStringInfoChar(buf, '.'); + + if (v->anybounds.first == 0 && + v->anybounds.last == PG_UINT32_MAX) + appendBinaryStringInfo(buf, "**", 2); + else if (v->anybounds.first == 0) + appendStringInfo(buf, "**{,%u}", v->anybounds.last); + else if (v->anybounds.last == PG_UINT32_MAX) + appendStringInfo(buf, "**{%u,}", v->anybounds.first); + else if (v->anybounds.first == v->anybounds.last) + appendStringInfo(buf, "**{%u}", v->anybounds.first); + else + appendStringInfo(buf, "**{%u,%u}", v->anybounds.first, + v->anybounds.last); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); } @@ -348,6 +373,10 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32(v->array.nelems, base, pos); read_int32_n(v->array.elems, base, pos, v->array.nelems); break; + case jpiAny: + read_int32(v->anybounds.first, base, pos); + read_int32(v->anybounds.last, base, pos); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); } @@ -376,6 +405,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) { Assert( v->type == jpiKey || + v->type == jpiAny || v->type == jpiAnyArray || v->type == jpiAnyKey || v->type == jpiIndexArray || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 67e044b802..245e2fd977 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -295,6 +295,67 @@ copyJsonbValue(JsonbValue *src) return dst; } +static JsonPathExecResult +recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found); + +static JsonPathExecResult +recursiveAny(JsonPathItem *jsp, List *vars, JsonbValue *jb, + List **found, uint32 level, uint32 first, uint32 last) +{ + JsonPathExecResult res = jperNotFound; + JsonbIterator *it; + int32 r; + JsonbValue v; + + check_stack_depth(); + + if (level > last) + return res; + + it = JsonbIteratorInit(jb->val.binary.data); + + while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_KEY) + { + r = JsonbIteratorNext(&it, &v, true); + Assert(r == WJB_VALUE); + } + + if (r == WJB_VALUE || r == WJB_ELEM) + { + + if (level >= first) + { + /* check expression */ + if (jsp) + { + res = recursiveExecute(jsp, vars, &v, found); + if (res == jperOk && !found) + break; + } + else + { + res = jperOk; + if (!found) + break; + *found = lappend(*found, copyJsonbValue(&v)); + } + } + + if (level < last && v.type == jbvBinary) + { + res = recursiveAny(jsp, vars, &v, found, level + 1, first, last); + + if (res == jperOk && found == NULL) + break; + } + } + } + + return res; +} + static JsonPathExecResult recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { @@ -524,6 +585,35 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) if (res == jperOk && found) *found = lappend(*found, copyJsonbValue(jb)); break; + case jpiAny: + { + bool hasNext = jspGetNext(jsp, &elem); + + /* first try without any intermediate steps */ + if (jsp->anybounds.first == 0) + { + if (hasNext) + { + res = recursiveExecute(&elem, vars, jb, found); + if (res == jperOk && !found) + break; + } + else + { + res = jperOk; + if (!found) + break; + *found = lappend(*found, copyJsonbValue(jb)); + } + } + + if (jb->type == jbvBinary) + res = recursiveAny(hasNext ? &elem : NULL, vars, jb, found, + 1, + jsp->anybounds.first, + jsp->anybounds.last); + 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 51e5b77ec1..e36ca0e10b 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -173,6 +173,17 @@ makeIndexArray(List *list) return v; } +static JsonPathParseItem* +makeAny(int first, int last) +{ + JsonPathParseItem *v = makeItemType(jpiAny); + + v->anybounds.first = (first > 0) ? first : 0; + v->anybounds.last = (last >= 0) ? last : PG_UINT32_MAX; + + return v; +} + %} /* BISON Declarations */ @@ -289,9 +300,16 @@ array_accessors: ; any_key: - key { $$ = $1; } - | array_accessor { $$ = $1; } - | '*' { $$ = makeItemType(jpiAnyKey); } + key { $$ = $1; } + | array_accessor { $$ = $1; } + | '*' { $$ = makeItemType(jpiAnyKey); } + | '*' '*' { $$ = makeAny(-1, -1); } + | '*' '*' '{' INT_P '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), + pg_atoi($4.val, 4, 0)); } + | '*' '*' '{' ',' INT_P '}' { $$ = makeAny(-1, pg_atoi($5.val, 4, 0)); } + | '*' '*' '{' INT_P ',' '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), -1); } + | '*' '*' '{' INT_P ',' INT_P '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), + pg_atoi($6.val, 4, 0)); } ; joined_key: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 0baaf95a5e..a6471195b6 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -64,8 +64,8 @@ fprintf_to_ereport(const char *fmt, const char *msg) %x xVARQUOTED %x xCOMMENT -special [\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/] -any [^\?\%\$\.\[\]\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f] +special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/] +any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f] blank [ \t\n\r\f] unicode \\u[0-9A-Fa-f]{4} diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index f9d4efac62..3fa759f5bf 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -45,7 +45,7 @@ typedef enum JsonPathItemType { jpiAnyArray, jpiAnyKey, jpiIndexArray, - //jpiAny, + jpiAny, //jpiAll, //jpiAllArray, //jpiAllKey, @@ -74,7 +74,7 @@ typedef struct JsonPathItem { union { struct { char *data; /* for bool, numeric and string/key */ - int datalen; /* filled only for string/key */ + int32 datalen; /* filled only for string/key */ } value; struct { @@ -85,9 +85,14 @@ typedef struct JsonPathItem { int32 arg; struct { - int nelems; + int32 nelems; int32 *elems; } array; + + struct { + uint32 first; + uint32 last; + } anybounds; }; } JsonPathItem; @@ -122,14 +127,19 @@ struct JsonPathParseItem { Numeric numeric; bool boolean; struct { - uint32 len; - char *val; /* could not be not null-terminated */ + uint32 len; + char *val; /* could not be not null-terminated */ } string; struct { - int nelems; - int32 *elems; + int nelems; + int32 *elems; } array; + + struct { + uint32 first; + uint32 last; + } anybounds; }; }; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 440b1b4d82..350a0a18ee 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -40,6 +40,24 @@ select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); t (1 row) +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{2}'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{3}'); + _jsonpath_exists +------------------ + f +(1 row) + select _jsonpath_exists(jsonb '[]', '$.[*]'); _jsonpath_exists ------------------ @@ -190,3 +208,194 @@ select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)' "1" (1 row) +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); + _jsonpath_query +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}'); + _jsonpath_query +----------------- + {"b": 1} +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}'); + _jsonpath_query +----------------- + {"b": 1} + 1 +(2 rows) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2}'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2,}'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{3,}'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + f +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + f +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + f +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 0761cc7835..e5e36ecd97 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -105,6 +105,42 @@ select '$[*][0].a.b'::jsonpath; $[*][0]."a"."b" (1 row) +select '$.a.**.b'::jsonpath; + jsonpath +-------------- + $."a".**."b" +(1 row) + +select '$.a.**{2}.b'::jsonpath; + jsonpath +----------------- + $."a".**{2}."b" +(1 row) + +select '$.a.**{2,2}.b'::jsonpath; + jsonpath +----------------- + $."a".**{2}."b" +(1 row) + +select '$.a.**{2,5}.b'::jsonpath; + jsonpath +------------------- + $."a".**{2,5}."b" +(1 row) + +select '$.a.**{,5}.b'::jsonpath; + jsonpath +------------------ + $."a".**{,5}."b" +(1 row) + +select '$.a.**{5,}.b'::jsonpath; + jsonpath +------------------ + $."a".**{5,}."b" +(1 row) + select '$.g ? (@ = 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 1ab808ab10..ffa99bfb1b 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -5,6 +5,10 @@ select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); select _jsonpath_exists(jsonb '{}', '$.*'); select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{2}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{3}'); + select _jsonpath_exists(jsonb '[]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[1]'); @@ -32,3 +36,37 @@ select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $valu select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); + +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{3,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); + +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 75d31d78ef..88c3488ffa 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -18,6 +18,12 @@ select '$[0]'::jsonpath; select '$[*][0]'::jsonpath; select '$[*].a'::jsonpath; select '$[*][0].a.b'::jsonpath; +select '$.a.**.b'::jsonpath; +select '$.a.**{2}.b'::jsonpath; +select '$.a.**{2,2}.b'::jsonpath; +select '$.a.**{2,5}.b'::jsonpath; +select '$.a.**{,5}.b'::jsonpath; +select '$.a.**{5,}.b'::jsonpath; select '$.g ? (@ = 1)'::jsonpath; select '$.g ? (a = 1)'::jsonpath; From b6c7c94c9d4393f0f2c714ffbd5899a9447d62e4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Feb 2017 15:04:06 +0300 Subject: [PATCH 12/97] Fix jsonpath grammar: add multiple filters support, expressions, comprasion ops for sequences --- src/backend/utils/adt/jsonpath.c | 159 +++++++++--- src/backend/utils/adt/jsonpath_exec.c | 259 ++++++++++++++----- src/backend/utils/adt/jsonpath_gram.y | 190 ++++++++------ src/backend/utils/adt/jsonpath_scan.l | 15 +- src/include/utils/jsonpath.h | 11 +- src/test/regress/expected/jsonb_jsonpath.out | 43 ++- src/test/regress/expected/jsonpath.out | 68 +++-- src/test/regress/sql/jsonb_jsonpath.sql | 12 +- src/test/regress/sql/jsonpath.sql | 7 +- 9 files changed, 549 insertions(+), 215 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 6c864090ec..3db17b8cd5 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -50,6 +50,17 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) break; case jpiAnd: case jpiOr: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: { int32 left, right; @@ -64,13 +75,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) *(int32*)(buf->data + right) = chld; } break; - case jpiEqual: - case jpiLess: - case jpiGreater: - case jpiLessOrEqual: - case jpiGreaterOrEqual: case jpiNot: - case jpiExpression: + case jpiIsUnknown: + case jpiPlus: + case jpiMinus: + case jpiFilter: { int32 arg; @@ -150,7 +159,9 @@ printOperation(StringInfo buf, JsonPathItemType type) case jpiOr: appendBinaryStringInfo(buf, " || ", 4); break; case jpiEqual: - appendBinaryStringInfo(buf, " = ", 3); break; + appendBinaryStringInfo(buf, " = ", 3); break; /* FIXME == */ + case jpiNotEqual: + appendBinaryStringInfo(buf, " != ", 4); break; case jpiLess: appendBinaryStringInfo(buf, " < ", 3); break; case jpiGreater: @@ -159,11 +170,49 @@ printOperation(StringInfo buf, JsonPathItemType type) appendBinaryStringInfo(buf, " <= ", 4); break; case jpiGreaterOrEqual: appendBinaryStringInfo(buf, " >= ", 4); break; + case jpiAdd: + appendBinaryStringInfo(buf, " + ", 3); break; + case jpiSub: + appendBinaryStringInfo(buf, " - ", 3); break; + case jpiMul: + appendBinaryStringInfo(buf, " * ", 3); break; + case jpiDiv: + appendBinaryStringInfo(buf, " / ", 3); break; + case jpiMod: + appendBinaryStringInfo(buf, " % ", 3); break; default: elog(ERROR, "Unknown jsonpath item type: %d", type); } } +static int +operationPriority(JsonPathItemType op) +{ + switch (op) + { + case jpiOr: + return 0; + case jpiAnd: + return 1; + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + return 2; + case jpiAdd: + case jpiSub: + return 3; + case jpiMul: + case jpiDiv: + case jpiMod: + return 4; + default: + return 5; + } +} + static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes) { @@ -202,33 +251,41 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket break; case jpiAnd: case jpiOr: - appendStringInfoChar(buf, '('); + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + if (printBracketes) + appendStringInfoChar(buf, '('); jspGetLeftArg(v, &elem); - printJsonPathItem(buf, &elem, false, true); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); printOperation(buf, v->type); jspGetRightArg(v, &elem); - printJsonPathItem(buf, &elem, false, true); - appendStringInfoChar(buf, ')'); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + if (printBracketes) + appendStringInfoChar(buf, ')'); break; - case jpiExpression: + case jpiFilter: appendBinaryStringInfo(buf, "?(", 2); jspGetArg(v, &elem); printJsonPathItem(buf, &elem, false, false); appendStringInfoChar(buf, ')'); break; - case jpiEqual: - case jpiLess: - case jpiGreater: - case jpiLessOrEqual: - case jpiGreaterOrEqual: - printOperation(buf, v->type); - jspGetArg(v, &elem); - printJsonPathItem(buf, &elem, false, true); - break; case jpiNot: - appendBinaryStringInfo(buf, "(! ", 2); + appendBinaryStringInfo(buf, "!(", 2); jspGetArg(v, &elem); - printJsonPathItem(buf, &elem, false, true); + printJsonPathItem(buf, &elem, false, false); appendStringInfoChar(buf, ')'); break; case jpiCurrent: @@ -357,16 +414,25 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) break; case jpiAnd: case jpiOr: - read_int32(v->args.left, base, pos); - read_int32(v->args.right, base, pos); - break; + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: case jpiEqual: + case jpiNotEqual: case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: + read_int32(v->args.left, base, pos); + read_int32(v->args.right, base, pos); + break; case jpiNot: - case jpiExpression: + case jpiIsUnknown: + case jpiMinus: + case jpiPlus: + case jpiFilter: read_int32(v->arg, base, pos); break; case jpiIndexArray: @@ -386,13 +452,11 @@ void jspGetArg(JsonPathItem *v, JsonPathItem *a) { Assert( - v->type == jpiEqual || - v->type == jpiLess || - v->type == jpiGreater || - v->type == jpiLessOrEqual || - v->type == jpiGreaterOrEqual || - v->type == jpiExpression || - v->type == jpiNot + v->type == jpiFilter || + v->type == jpiNot || + v->type == jpiIsUnknown || + v->type == jpiPlus || + v->type == jpiMinus ); jspInitByBuffer(a, v->base, v->arg); @@ -409,6 +473,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiAnyArray || v->type == jpiAnyKey || v->type == jpiIndexArray || + v->type == jpiFilter || v->type == jpiCurrent || v->type == jpiRoot ); @@ -426,7 +491,18 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) { Assert( v->type == jpiAnd || - v->type == jpiOr + v->type == jpiOr || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiLess || + v->type == jpiGreater || + v->type == jpiLessOrEqual || + v->type == jpiGreaterOrEqual || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod ); jspInitByBuffer(a, v->base, v->args.left); @@ -437,7 +513,18 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) { Assert( v->type == jpiAnd || - v->type == jpiOr + v->type == jpiOr || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiLess || + v->type == jpiGreater || + v->type == jpiLessOrEqual || + v->type == jpiGreaterOrEqual || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod ); jspInitByBuffer(a, v->base, v->args.right); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 245e2fd977..0d800d3e62 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -19,6 +19,9 @@ #include "utils/json.h" #include "utils/jsonpath.h" +static JsonPathExecResult +recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found); + /********************Execute functions for JsonPath***************************/ static void @@ -188,101 +191,192 @@ compareNumeric(Numeric a, Numeric b) ) ); } + static bool -checkScalarEquality(JsonPathItem *jsp, List *vars, JsonbValue *jb) +checkScalarEquality(JsonbValue *jb1, JsonbValue *jb2) { - JsonbValue computedValue; - - if (jb->type == jbvBinary) - return false; - - computeJsonPathItem(jsp, vars, &computedValue); - - if (jb->type != computedValue.type) - return false; - - switch(computedValue.type) + switch (jb1->type) { case jbvNull: return true; case jbvString: - return (computedValue.val.string.len == jb->val.string.len && - memcmp(jb->val.string.val, computedValue.val.string.val, - computedValue.val.string.len) == 0); + return (jb1->val.string.len == jb2->val.string.len && + memcmp(jb2->val.string.val, jb1->val.string.val, + jb1->val.string.len) == 0); case jbvBool: - return (jb->val.boolean == computedValue.val.boolean); + return (jb2->val.boolean == jb1->val.boolean); case jbvNumeric: - return (compareNumeric(computedValue.val.numeric, jb->val.numeric) == 0); + return (compareNumeric(jb1->val.numeric, jb2->val.numeric) == 0); default: elog(ERROR,"Wrong state"); + return false; + } +} + +static JsonPathExecResult +checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) +{ + bool eq; + + if (jb1->type != jb2->type) + { + if (jb1->type == jbvNull || jb2->type == jbvNull) + return not ? jperOk : jperNotFound; + + return jperError; } - return false; + if (jb1->type == jbvBinary) + return jperError; + /* + eq = compareJsonbContainers(jb1->val.binary.data, + jb2->val.binary.data) == 0; + */ + else + eq = checkScalarEquality(jb1, jb2); + + return !!not ^ !!eq ? jperOk : jperNotFound; } -static bool -makeCompare(JsonPathItem *jsp, List *vars, int32 op, JsonbValue *jb) +static JsonPathExecResult +makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) { - int res; - JsonbValue computedValue; + int cmp; + bool res; - if (jb->type != jbvNumeric) - return false; + if (jb1->type != jb2->type) + { + if (jb1->type != jbvNull && jb2->type != jbvNull) + /* non-null items of different types are not order-comparable */ + return jperError; - computeJsonPathItem(jsp, vars, &computedValue); + if (jb1->type != jbvNull || jb2->type != jbvNull) + /* comparison of nulls to non-nulls returns always false */ + return jperNotFound; - if (computedValue.type != jbvNumeric) - return false; + /* both values are JSON nulls */ + } - res = compareNumeric(jb->val.numeric, computedValue.val.numeric); + switch (jb1->type) + { + case jbvNull: + cmp = 0; + break; + case jbvNumeric: + cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric); + break; + /* + case jbvString: + cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len, + jb2->val.string.val, jb2->val.string.len, + collationId); + break; + */ + default: + return jperError; + } - switch(op) + switch (op) { case jpiEqual: - return (res == 0); + res = (cmp == 0); + break; + case jpiNotEqual: + res = (cmp != 0); + break; case jpiLess: - return (res < 0); + res = (cmp < 0); + break; case jpiGreater: - return (res > 0); + res = (cmp > 0); + break; case jpiLessOrEqual: - return (res <= 0); + res = (cmp <= 0); + break; case jpiGreaterOrEqual: - return (res >= 0); + res = (cmp >= 0); + break; default: elog(ERROR, "Unknown operation"); + return jperError; } - return false; + return res ? jperOk : jperNotFound; } static JsonPathExecResult -executeExpr(JsonPathItem *jsp, List *vars, int32 op, JsonbValue *jb) +executeExpr(JsonPathItem *jsp, List *vars, JsonbValue *jb) { - JsonPathExecResult res = jperNotFound; - /* - * read arg type - */ - Assert(jspGetNext(jsp, NULL) == false); - Assert(jsp->type == jpiString || jsp->type == jpiNumeric || - jsp->type == jpiNull || jsp->type == jpiBool || - jsp->type == jpiVariable); - - switch(op) + JsonPathExecResult res; + JsonPathItem elem; + List *lseq = NIL; + List *rseq = NIL; + ListCell *llc; + ListCell *rlc; + bool strict = true; /* FIXME pass */ + bool error = false; + bool found = false; + + jspGetLeftArg(jsp, &elem); + res = recursiveExecute(&elem, vars, jb, &lseq); + if (res != jperOk) + return res; + + jspGetRightArg(jsp, &elem); + res = recursiveExecute(&elem, vars, jb, &rseq); + if (res != jperOk) + return res; + + foreach(llc, lseq) { - case jpiEqual: - res = checkScalarEquality(jsp, vars, jb) ? jperOk : jperNotFound; - break; - case jpiLess: - case jpiGreater: - case jpiLessOrEqual: - case jpiGreaterOrEqual: - res = makeCompare(jsp, vars, op, jb) ? jperOk : jperNotFound; - break; - default: - elog(ERROR, "Unknown operation"); + JsonbValue *lval = lfirst(llc); + + foreach(rlc, rseq) + { + JsonbValue *rval = lfirst(rlc); + + switch (jsp->type) + { + case jpiEqual: + res = checkEquality(lval, rval, false); + break; + case jpiNotEqual: + res = checkEquality(lval, rval, true); + break; + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + res = makeCompare(jsp->type, lval, rval); + break; + default: + elog(ERROR, "Unknown operation"); + } + + if (res == jperOk) + { + if (!strict) + return jperOk; + + found = true; + } + else if (res == jperError) + { + if (strict) + return jperError; + + error = true; + } + } } - return res; + if (found) /* possible only in strict mode */ + return jperOk; + + if (error) /* possible only in non-strict mode */ + return jperError; + + return jperNotFound; } static JsonbValue* @@ -426,18 +520,33 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) } break; case jpiCurrent: - jspGetNext(jsp, &elem); - if (JsonbType(jb) == jbvScalar) + if (!jspGetNext(jsp, &elem)) + { + res = jperOk; + if (found) + { + JsonbValue *v; + + if (JsonbType(jb) == jbvScalar) + v = JsonbExtractScalar(jb->val.binary.data, + palloc(sizeof(*v))); + else + v = copyJsonbValue(jb); /* FIXME */ + + *found = lappend(*found, v); + } + } + else if (JsonbType(jb) == jbvScalar) { JsonbValue v; JsonbExtractScalar(jb->val.binary.data, &v); - res = recursiveExecute(&elem, vars, &v, NULL); + res = recursiveExecute(&elem, vars, &v, found); } else { - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(&elem, vars, jb, found); } break; case jpiAnyArray: @@ -558,12 +667,12 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) } break; case jpiEqual: + case jpiNotEqual: case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: - jspGetArg(jsp, &elem); - res = executeExpr(&elem, vars, jsp->type, jb); + res = executeExpr(jsp, vars, jb); break; case jpiRoot: if (jspGetNext(jsp, &elem)) @@ -578,11 +687,14 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) } break; - case jpiExpression: - /* no-op actually */ + case jpiFilter: jspGetArg(jsp, &elem); res = recursiveExecute(&elem, vars, jb, NULL); - if (res == jperOk && found) + if (res != jperOk) + res = jperNotFound; + else if (jspGetNext(jsp, &elem)) + res = recursiveExecute(&elem, vars, jb, found); + else if (found) *found = lappend(*found, copyJsonbValue(jb)); break; case jpiAny: @@ -614,6 +726,19 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) jsp->anybounds.last); break; } + case jpiNull: + case jpiBool: + case jpiNumeric: + case jpiString: + case jpiVariable: + res = jperOk; + if (found) + { + JsonbValue *jbv = palloc(sizeof(*jbv)); + computeJsonPathItem(jsp, vars, jbv); + *found = lappend(*found, jbv); + } + 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 e36ca0e10b..40f6ece727 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -119,7 +119,21 @@ makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra) static JsonPathParseItem* makeItemUnary(int type, JsonPathParseItem* a) { - JsonPathParseItem *v = makeItemType(type); + JsonPathParseItem *v; + + if (type == jpiPlus && a->type == jpiNumeric && !a->next) + return a; + + if (type == jpiMinus && a->type == jpiNumeric && !a->next) + { + v = makeItemType(jpiNumeric); + v->numeric = + DatumGetNumeric(DirectFunctionCall1(numeric_uminus, + NumericGetDatum(a->numeric))); + return v; + } + + v = makeItemType(type); v->arg = a; @@ -147,14 +161,6 @@ makeItemList(List *list) { return head; } -static JsonPathParseItem* -makeItemExpression(List *path, JsonPathParseItem *right_expr) -{ - JsonPathParseItem *expr = makeItemUnary(jpiExpression, right_expr); - - return makeItemList(lappend(path, expr)); -} - static JsonPathParseItem* makeIndexArray(List *list) { @@ -198,23 +204,29 @@ makeAny(int first, int last) List *elems; /* list of JsonPathParseItem */ List *indexs; JsonPathParseItem *value; + int optype; } %token TO_P NULL_P TRUE_P FALSE_P %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P +%token ANY_P -%type result scalar_value - -%type joined_key path absolute_path relative_path array_accessors +%type result jsonpath scalar_value path_primary expr + array_accessor any_path accessor_op key unary_expr + predicate delimited_predicate // numeric -%type key any_key right_expr expr jsonpath numeric array_accessor +%type accessor_expr /* path absolute_path relative_path */ %type index_elem index_list +%type comp_op + %left OR_P %left AND_P %right NOT_P +%left '+' '-' +%left '*' '/' '%' %nonassoc '(' ')' /* Grammar follows */ @@ -236,52 +248,87 @@ scalar_value: | VARIABLE_P { $$ = makeItemVariable(&$1); } ; +/* numeric: NUMERIC_P { $$ = makeItemNumeric(&$1); } | INT_P { $$ = makeItemNumeric(&$1); } | VARIABLE_P { $$ = makeItemVariable(&$1); } ; +*/ -right_expr: - '=' scalar_value { $$ = makeItemUnary(jpiEqual, $2); } - | '<' numeric { $$ = makeItemUnary(jpiLess, $2); } - | '>' numeric { $$ = makeItemUnary(jpiGreater, $2); } - | '<' '=' numeric { $$ = makeItemUnary(jpiLessOrEqual, $3); } - | '>' '=' numeric { $$ = makeItemUnary(jpiGreaterOrEqual, $3); } +jsonpath: + expr ; -jsonpath: - absolute_path { $$ = makeItemList($1); } - | absolute_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } - | relative_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } +comp_op: + '=' { $$ = jpiEqual; } + | '<' '>' { $$ = jpiNotEqual; } + | '<' { $$ = jpiLess; } + | '>' { $$ = jpiGreater; } + | '<' '=' { $$ = jpiLessOrEqual; } + | '>' '=' { $$ = jpiGreaterOrEqual; } ; +delimited_predicate: + '(' predicate ')' { $$ = $2; } +// | EXISTS '(' relative_path ')' { $$ = makeItemUnary(jpiExists, $2); } + ; + +predicate: + delimited_predicate { $$ = $1; } + | expr comp_op expr { $$ = makeItemBinary($2, $1, $3); } +// | expr LIKE_REGEX pattern { $$ = ...; } +// | expr STARTS WITH STRING_P { $$ = ...; } +// | expr STARTS WITH '$' STRING_P { $$ = ...; } +// | expr STARTS WITH '$' STRING_P { $$ = ...; } +// | '.' any_key right_expr { $$ = makeItemList(list_make2($2, $3)); } +// | '(' predicate ')' IS UNKNOWN { $$ = makeItemUnary(jpiIsUnknown, $2); } + | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); } + | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); } + | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); } + ; + +path_primary: + scalar_value { $$ = $1; } + | '$' { $$ = makeItemType(jpiRoot); } + | '@' { $$ = makeItemType(jpiCurrent); } + ; + +accessor_expr: + path_primary { $$ = list_make1($1); } + | '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); } + | accessor_expr accessor_op { $$ = lappend($1, $2); } + ; + +unary_expr: + accessor_expr { $$ = makeItemList($1); } + | '+' unary_expr { $$ = makeItemUnary(jpiPlus, $2); } + | '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); } + ; + +// | '(' expr ')' { $$ = $2; } + expr: - any_key right_expr { $$ = makeItemList(list_make2($1, $2)); } - | '.' any_key right_expr { $$ = makeItemList(list_make2($2, $3)); } - | '@' right_expr - { $$ = makeItemList(list_make2(makeItemType(jpiCurrent), $2)); } - | '@' '.' any_key right_expr - { $$ = makeItemList(list_make3(makeItemType(jpiCurrent),$3, $4)); } - | relative_path '?' '(' expr ')' { $$ = makeItemExpression($1, $4); } - | '(' expr ')' { $$ = $2; } - | expr AND_P expr { $$ = makeItemBinary(jpiAnd, $1, $3); } - | expr OR_P expr { $$ = makeItemBinary(jpiOr, $1, $3); } - | NOT_P expr { $$ = makeItemUnary(jpiNot, $2); } + unary_expr { $$ = $1; } + | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); } + | expr '-' expr { $$ = makeItemBinary(jpiSub, $1, $3); } + | expr '*' expr { $$ = makeItemBinary(jpiMul, $1, $3); } + | expr '/' expr { $$ = makeItemBinary(jpiDiv, $1, $3); } + | expr '%' expr { $$ = makeItemBinary(jpiMod, $1, $3); } ; index_elem: - INT_P { $$ = list_make1_int(pg_atoi($1.val, 4, 0)); } - | INT_P TO_P INT_P { - int start = pg_atoi($1.val, 4, 0), - stop = pg_atoi($3.val, 4, 0), - i; + INT_P { $$ = list_make1_int(pg_atoi($1.val, 4, 0)); } + | INT_P TO_P INT_P { + int start = pg_atoi($1.val, 4, 0), + stop = pg_atoi($3.val, 4, 0), + i; - $$ = NIL; + $$ = NIL; - for(i=start; i<= stop; i++) - $$ = lappend_int($$, i); - } + for(i=start; i<= stop; i++) + $$ = lappend_int($$, i); + } ; index_list: @@ -294,27 +341,23 @@ array_accessor: | '[' index_list ']' { $$ = makeIndexArray($2); } ; -array_accessors: - array_accessor { $$ = list_make1($1); } - | array_accessors array_accessor { $$ = lappend($1, $2); } - ; - -any_key: - key { $$ = $1; } - | array_accessor { $$ = $1; } - | '*' { $$ = makeItemType(jpiAnyKey); } - | '*' '*' { $$ = makeAny(-1, -1); } - | '*' '*' '{' INT_P '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), - pg_atoi($4.val, 4, 0)); } - | '*' '*' '{' ',' INT_P '}' { $$ = makeAny(-1, pg_atoi($5.val, 4, 0)); } - | '*' '*' '{' INT_P ',' '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), -1); } - | '*' '*' '{' INT_P ',' INT_P '}' { $$ = makeAny(pg_atoi($4.val, 4, 0), - pg_atoi($6.val, 4, 0)); } +any_path: + ANY_P { $$ = makeAny(-1, -1); } + | ANY_P '{' INT_P '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), + pg_atoi($3.val, 4, 0)); } + | ANY_P '{' ',' INT_P '}' { $$ = makeAny(-1, pg_atoi($4.val, 4, 0)); } + | ANY_P '{' INT_P ',' '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), -1); } + | ANY_P '{' INT_P ',' INT_P '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), + pg_atoi($5.val, 4, 0)); } ; -joined_key: - any_key { $$ = list_make1($1); } - | joined_key array_accessor { $$ = lappend($1, $2); } +accessor_op: + '.' key { $$ = $2; } + | '.' '*' { $$ = makeItemType(jpiAnyKey); } + | array_accessor { $$ = $1; } + | '.' array_accessor { $$ = $2; } + | '.' any_path { $$ = $2; } + | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } ; key: @@ -324,26 +367,23 @@ key: | TRUE_P { $$ = makeItemKey(&$1); } | FALSE_P { $$ = makeItemKey(&$1); } ; - +/* absolute_path: - '$' '.' { $$ = list_make1(makeItemType(jpiRoot)); } - | '$' { $$ = list_make1(makeItemType(jpiRoot)); } - | '$' '.' path { $$ = lcons(makeItemType(jpiRoot), $3); } - | '$' array_accessors { $$ = lcons(makeItemType(jpiRoot), $2); } - | '$' array_accessors '.' path { $$ = lcons(makeItemType(jpiRoot), list_concat($2, $4)); } + '$' { $$ = list_make1(makeItemType(jpiRoot)); } + | '$' path { $$ = lcons(makeItemType(jpiRoot), $2); } ; relative_path: - joined_key '.' joined_key { $$ = list_concat($1, $3); } - | '.' joined_key '.' joined_key { $$ = list_concat($2, $4); } - | '@' '.' joined_key '.' joined_key { $$ = list_concat($3, $5); } - | relative_path '.' joined_key { $$ = list_concat($1, $3); } + key { $$ = list_make1(makeItemType(jpiCurrent), $1); } + | key path { $$ = lcons(makeItemType(jpiCurrent), lcons($1, $2)); } + | '@' { $$ = list_make1(makeItemType(jpiCurrent)); } + | '@' path { $$ = lcons(makeItemType(jpiCurrent), $2); } ; path: - joined_key { $$ = $1; } - | path '.' joined_key { $$ = list_concat($1, $3); } + accessor_op { $$ = list_make($1); } + | path accessor_op { $$ = lappend($1, $2); } ; - +*/ %% diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index a6471195b6..373dc29f8d 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -77,6 +77,8 @@ unicode \\u[0-9A-Fa-f]{4} \! { return NOT_P; } +\*\* { return ANY_P; } + \${any}+ { addstring(true, yytext + 1, yyleng - 1); addchar(false, '\0'); @@ -98,28 +100,21 @@ unicode \\u[0-9A-Fa-f]{4} BEGIN xCOMMENT; } -[+-]?[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ { - addstring(true, yytext, yyleng); - addchar(false, '\0'); - yylval->str = scanstring; - return NUMERIC_P; - } - -[+-]?\.[0-9]+[eE][+-]?[0-9]+ /* float */ { +[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ { addstring(true, yytext, yyleng); addchar(false, '\0'); yylval->str = scanstring; return NUMERIC_P; } -[+-]?([0-9]+)?\.[0-9]+ { +\.[0-9]+[eE][+-]?[0-9]+ /* float */ { addstring(true, yytext, yyleng); addchar(false, '\0'); yylval->str = scanstring; return NUMERIC_P; } -[+-][0-9]+ { +([0-9]+)?\.[0-9]+ { addstring(true, yytext, yyleng); addchar(false, '\0'); yylval->str = scanstring; diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 3fa759f5bf..31ed953b48 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -37,11 +37,20 @@ typedef enum JsonPathItemType { jpiAnd, jpiOr, jpiNot, + jpiIsUnknown, jpiEqual, + jpiNotEqual, jpiLess, jpiGreater, jpiLessOrEqual, jpiGreaterOrEqual, + jpiAdd, + jpiSub, + jpiMul, + jpiDiv, + jpiMod, + jpiPlus, + jpiMinus, jpiAnyArray, jpiAnyKey, jpiIndexArray, @@ -53,7 +62,7 @@ typedef enum JsonPathItemType { jpiCurrent, jpiRoot, jpiVariable, - jpiExpression + jpiFilter, } JsonPathItemType; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 350a0a18ee..7b15577214 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -82,6 +82,30 @@ select _jsonpath_exists(jsonb '[1]', '$.[0]'); t (1 row) +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); + _jsonpath_exists +------------------ + t +(1 row) + select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); _jsonpath_query ----------------- @@ -154,15 +178,15 @@ select * from _jsonpath_query(jsonb '{"a": 10}', '$'); {"a": 10} (1 row) -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); ERROR: could not find 'value' passed variable -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); _jsonpath_query ----------------- {"a": 10} (1 row) -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 8}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); _jsonpath_query ----------------- (0 rows) @@ -208,6 +232,19 @@ select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)' "1" (1 row) +select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); + _jsonpath_query +----------------- + 1 + "2" +(2 rows) + +select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); + _jsonpath_query +----------------- + null +(1 row) + select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); _jsonpath_query ----------------- diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index e5e36ecd97..da24cf81ba 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -141,6 +141,30 @@ select '$.a.**{5,}.b'::jsonpath; $."a".**{5,}."b" (1 row) +select '$+1'::jsonpath; + jsonpath +---------- + ($ + 1) +(1 row) + +select '$-1'::jsonpath; + jsonpath +---------- + ($ - 1) +(1 row) + +select '$--+1'::jsonpath; + jsonpath +---------- + ($ - -1) +(1 row) + +select '$.a/+-1'::jsonpath; + jsonpath +-------------- + ($."a" / -1) +(1 row) + select '$.g ? (@ = 1)'::jsonpath; jsonpath --------------- @@ -154,9 +178,9 @@ select '$.g ? (a = 1)'::jsonpath; (1 row) select '$.g ? (.a = 1)'::jsonpath; - jsonpath ------------------ - $."g"?("a" = 1) + jsonpath +------------------- + $."g"?(@."a" = 1) (1 row) select '$.g ? (@.a = 1)'::jsonpath; @@ -166,33 +190,39 @@ select '$.g ? (@.a = 1)'::jsonpath; (1 row) select '$.g ? (@.a = 1 || a = 4)'::jsonpath; - jsonpath --------------------------------- - $."g"?((@."a" = 1 || "a" = 4)) + jsonpath +------------------------------ + $."g"?(@."a" = 1 || "a" = 4) (1 row) select '$.g ? (@.a = 1 && a = 4)'::jsonpath; - jsonpath --------------------------------- - $."g"?((@."a" = 1 && "a" = 4)) + jsonpath +------------------------------ + $."g"?(@."a" = 1 && "a" = 4) (1 row) select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; - jsonpath ---------------------------------------------- - $."g"?((@."a" = 1 || ("a" = 4 && "b" = 7))) + jsonpath +----------------------------------------- + $."g"?(@."a" = 1 || "a" = 4 && "b" = 7) (1 row) -select '$.g ? (@.a = 1 || !a = 4 && b = 7)'::jsonpath; - jsonpath ------------------------------------------------- - $."g"?((@."a" = 1 || ((!"a" = 4) && "b" = 7))) +select '$.g ? (@.a = 1 || !(a = 4) && b = 7)'::jsonpath; + jsonpath +-------------------------------------------- + $."g"?(@."a" = 1 || !("a" = 4) && "b" = 7) (1 row) select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; - jsonpath ----------------------------------------------------------------- - $."g"?((@."a" = 1 || ((!("x" >= 123 || "a" = 4)) && "b" = 7))) + jsonpath +---------------------------------------------------------- + $."g"?(@."a" = 1 || !("x" >= 123 || "a" = 4) && "b" = 7) +(1 row) + +select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; + jsonpath +--------------------------------------- + $."g"?(@."x" >= @[*]?(@."a" > "abc")) (1 row) select '$.g ? (zip = $zip)'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index ffa99bfb1b..d74e24dbf8 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -13,6 +13,10 @@ select _jsonpath_exists(jsonb '[]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[1]'); select _jsonpath_exists(jsonb '[1]', '$.[0]'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); @@ -27,15 +31,17 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); select * from _jsonpath_query(jsonb '{"a": 10}', '$'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 13}'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (a < $value)', '{"value" : 8}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); +select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); +select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); +select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 88c3488ffa..e4ab777ccf 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -24,6 +24,10 @@ select '$.a.**{2,2}.b'::jsonpath; select '$.a.**{2,5}.b'::jsonpath; select '$.a.**{,5}.b'::jsonpath; select '$.a.**{5,}.b'::jsonpath; +select '$+1'::jsonpath; +select '$-1'::jsonpath; +select '$--+1'::jsonpath; +select '$.a/+-1'::jsonpath; select '$.g ? (@ = 1)'::jsonpath; select '$.g ? (a = 1)'::jsonpath; @@ -32,8 +36,9 @@ select '$.g ? (@.a = 1)'::jsonpath; select '$.g ? (@.a = 1 || a = 4)'::jsonpath; select '$.g ? (@.a = 1 && a = 4)'::jsonpath; select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; -select '$.g ? (@.a = 1 || !a = 4 && b = 7)'::jsonpath; +select '$.g ? (@.a = 1 || !(a = 4) && b = 7)'::jsonpath; select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; +select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; select '$.g ? (zip = $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; From 5a056e56fb98555b092f2d650572884980ff9f5b Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 17:00:15 +0300 Subject: [PATCH 13/97] Use == instead of = --- src/backend/utils/adt/jsonpath.c | 6 +- src/backend/utils/adt/jsonpath_gram.y | 13 ++-- src/backend/utils/adt/jsonpath_scan.l | 14 ++++ src/test/regress/expected/jsonb_jsonpath.out | 4 +- src/test/regress/expected/jsonpath.out | 80 ++++++++++---------- src/test/regress/sql/jsonb_jsonpath.sql | 4 +- src/test/regress/sql/jsonpath.sql | 20 ++--- 7 files changed, 78 insertions(+), 63 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 3db17b8cd5..c72f3277aa 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -159,7 +159,7 @@ printOperation(StringInfo buf, JsonPathItemType type) case jpiOr: appendBinaryStringInfo(buf, " || ", 4); break; case jpiEqual: - appendBinaryStringInfo(buf, " = ", 3); break; /* FIXME == */ + appendBinaryStringInfo(buf, " == ", 4); break; case jpiNotEqual: appendBinaryStringInfo(buf, " != ", 4); break; case jpiLess: @@ -240,8 +240,8 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket break; case jpiNumeric: appendStringInfoString(buf, - DatumGetCString(DirectFunctionCall1(numeric_out, - PointerGetDatum(jspGetNumeric(v))))); + DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(jspGetNumeric(v))))); break; case jpiBool: if (jspGetBool(v)) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 40f6ece727..b01e505bfa 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -210,6 +210,7 @@ makeAny(int first, int last) %token TO_P NULL_P TRUE_P FALSE_P %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P +%token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P %type result jsonpath scalar_value path_primary expr @@ -261,12 +262,12 @@ jsonpath: ; comp_op: - '=' { $$ = jpiEqual; } - | '<' '>' { $$ = jpiNotEqual; } - | '<' { $$ = jpiLess; } - | '>' { $$ = jpiGreater; } - | '<' '=' { $$ = jpiLessOrEqual; } - | '>' '=' { $$ = jpiGreaterOrEqual; } + EQUAL_P { $$ = jpiEqual; } + | NOTEQUAL_P { $$ = jpiNotEqual; } + | LESS_P { $$ = jpiLess; } + | GREATER_P { $$ = jpiGreater; } + | LESSEQUAL_P { $$ = jpiLessOrEqual; } + | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; } ; delimited_predicate: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 373dc29f8d..0407584c89 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -79,6 +79,20 @@ unicode \\u[0-9A-Fa-f]{4} \*\* { return ANY_P; } +\< { return LESS_P; } + +\<\= { return LESSEQUAL_P; } + +\=\= { return EQUAL_P; } + +\<\> { return NOTEQUAL_P; } + +\!\= { return NOTEQUAL_P; } + +\>\= { return GREATEREQUAL_P; } + +\> { return GREATER_P; } + \${any}+ { addstring(true, yytext + 1, yyleng - 1); addchar(false, '\0'); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 7b15577214..2229ecd237 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -220,13 +220,13 @@ select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $v 12 (3 rows) -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); _jsonpath_query ----------------- "1" (1 row) -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); _jsonpath_query ----------------- "1" diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index da24cf81ba..4bd4e53b26 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -165,58 +165,58 @@ select '$.a/+-1'::jsonpath; ($."a" / -1) (1 row) -select '$.g ? (@ = 1)'::jsonpath; - jsonpath ---------------- - $."g"?(@ = 1) -(1 row) - -select '$.g ? (a = 1)'::jsonpath; - jsonpath ------------------ - $."g"?("a" = 1) +select '$.g ? (@ == 1)'::jsonpath; + jsonpath +---------------- + $."g"?(@ == 1) (1 row) -select '$.g ? (.a = 1)'::jsonpath; - jsonpath -------------------- - $."g"?(@."a" = 1) +select '$.g ? (a == 1)'::jsonpath; + jsonpath +------------------ + $."g"?("a" == 1) (1 row) -select '$.g ? (@.a = 1)'::jsonpath; - jsonpath -------------------- - $."g"?(@."a" = 1) +select '$.g ? (.a == 1)'::jsonpath; + jsonpath +-------------------- + $."g"?(@."a" == 1) (1 row) -select '$.g ? (@.a = 1 || a = 4)'::jsonpath; - jsonpath ------------------------------- - $."g"?(@."a" = 1 || "a" = 4) +select '$.g ? (@.a == 1)'::jsonpath; + jsonpath +-------------------- + $."g"?(@."a" == 1) (1 row) -select '$.g ? (@.a = 1 && a = 4)'::jsonpath; - jsonpath ------------------------------- - $."g"?(@."a" = 1 && "a" = 4) +select '$.g ? (@.a == 1 || a == 4)'::jsonpath; + jsonpath +-------------------------------- + $."g"?(@."a" == 1 || "a" == 4) (1 row) -select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; - jsonpath ------------------------------------------ - $."g"?(@."a" = 1 || "a" = 4 && "b" = 7) +select '$.g ? (@.a == 1 && a == 4)'::jsonpath; + jsonpath +-------------------------------- + $."g"?(@."a" == 1 && "a" == 4) (1 row) -select '$.g ? (@.a = 1 || !(a = 4) && b = 7)'::jsonpath; +select '$.g ? (@.a == 1 || a == 4 && b == 7)'::jsonpath; jsonpath -------------------------------------------- - $."g"?(@."a" = 1 || !("a" = 4) && "b" = 7) + $."g"?(@."a" == 1 || "a" == 4 && "b" == 7) +(1 row) + +select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; + jsonpath +----------------------------------------------- + $."g"?(@."a" == 1 || !("a" == 4) && "b" == 7) (1 row) -select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; - jsonpath ----------------------------------------------------------- - $."g"?(@."a" = 1 || !("x" >= 123 || "a" = 4) && "b" = 7) +select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; + jsonpath +------------------------------------------------------------- + $."g"?(@."a" == 1 || !("x" >= 123 || "a" == 4) && "b" == 7) (1 row) select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; @@ -225,10 +225,10 @@ select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; $."g"?(@."x" >= @[*]?(@."a" > "abc")) (1 row) -select '$.g ? (zip = $zip)'::jsonpath; - jsonpath ------------------------- - $."g"?("zip" = $"zip") +select '$.g ? (zip == $zip)'::jsonpath; + jsonpath +------------------------- + $."g"?("zip" == $"zip") (1 row) select '$.a.[1,2, 3 to 16]'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index d74e24dbf8..d4086f243a 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -38,8 +38,8 @@ select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = "1")'); -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ = $value)', '{"value" : "1"}'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); +select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index e4ab777ccf..87f2336438 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -29,18 +29,18 @@ select '$-1'::jsonpath; select '$--+1'::jsonpath; select '$.a/+-1'::jsonpath; -select '$.g ? (@ = 1)'::jsonpath; -select '$.g ? (a = 1)'::jsonpath; -select '$.g ? (.a = 1)'::jsonpath; -select '$.g ? (@.a = 1)'::jsonpath; -select '$.g ? (@.a = 1 || a = 4)'::jsonpath; -select '$.g ? (@.a = 1 && a = 4)'::jsonpath; -select '$.g ? (@.a = 1 || a = 4 && b = 7)'::jsonpath; -select '$.g ? (@.a = 1 || !(a = 4) && b = 7)'::jsonpath; -select '$.g ? (@.a = 1 || !(x >= 123 || a = 4) && b = 7)'::jsonpath; +select '$.g ? (@ == 1)'::jsonpath; +select '$.g ? (a == 1)'::jsonpath; +select '$.g ? (.a == 1)'::jsonpath; +select '$.g ? (@.a == 1)'::jsonpath; +select '$.g ? (@.a == 1 || a == 4)'::jsonpath; +select '$.g ? (@.a == 1 && a == 4)'::jsonpath; +select '$.g ? (@.a == 1 || a == 4 && b == 7)'::jsonpath; +select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; +select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; -select '$.g ? (zip = $zip)'::jsonpath; +select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; From 8bb9692140c9339f6aeb38e3ca97d74feee4615e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Feb 2017 18:53:04 +0300 Subject: [PATCH 14/97] Add jsonpath IS UNKNOWN predicate --- src/backend/utils/adt/jsonpath.c | 6 ++++++ src/backend/utils/adt/jsonpath_exec.c | 12 ++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 6 ++++-- src/backend/utils/adt/jsonpath_scan.l | 4 +++- src/test/regress/expected/jsonb_jsonpath.out | 12 ++++++++++++ src/test/regress/expected/jsonpath.out | 6 ++++++ src/test/regress/sql/jsonb_jsonpath.sql | 2 ++ src/test/regress/sql/jsonpath.sql | 1 + 8 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c72f3277aa..c3fd3e14c4 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -288,6 +288,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket printJsonPathItem(buf, &elem, false, false); appendStringInfoChar(buf, ')'); break; + case jpiIsUnknown: + appendStringInfoChar(buf, '('); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendBinaryStringInfo(buf, ") is unknown", 12); + break; case jpiCurrent: Assert(!inKey); appendStringInfoChar(buf, '@'); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0d800d3e62..9aa4aa0f30 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -491,6 +491,18 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) break; } break; + case jpiIsUnknown: + jspGetArg(jsp, &elem); + switch ((res = recursiveExecute(&elem, vars, jb, NULL))) + { + case jperError: + res = jperOk; + break; + default: + res = jperNotFound; + break; + } + break; case jpiKey: if (JsonbType(jb) == jbvObject) { diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index b01e505bfa..e5a4c2d1a7 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -207,7 +207,7 @@ makeAny(int first, int last) int optype; } -%token TO_P NULL_P TRUE_P FALSE_P +%token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P @@ -241,6 +241,8 @@ result: scalar_value: STRING_P { $$ = makeItemString(&$1); } | TO_P { $$ = makeItemString(&$1); } + | IS_P { $$ = makeItemString(&$1); } + | UNKNOWN_P { $$ = makeItemString(&$1); } | NULL_P { $$ = makeItemString(NULL); } | TRUE_P { $$ = makeItemBool(true); } | FALSE_P { $$ = makeItemBool(false); } @@ -283,7 +285,7 @@ predicate: // | expr STARTS WITH '$' STRING_P { $$ = ...; } // | expr STARTS WITH '$' STRING_P { $$ = ...; } // | '.' any_key right_expr { $$ = makeItemList(list_make2($2, $3)); } -// | '(' predicate ')' IS UNKNOWN { $$ = makeItemUnary(jpiIsUnknown, $2); } + | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); } | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); } | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); } diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 0407584c89..60a3031131 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -270,10 +270,12 @@ typedef struct keyword */ static keyword keywords[] = { + { 2, true, IS_P, "is"}, { 2, false, TO_P, "to"}, { 4, true, NULL_P, "null"}, { 4, true, TRUE_P, "true"}, - { 5, true, FALSE_P, "false"} + { 5, true, FALSE_P, "false"}, + { 7, true, UNKNOWN_P, "unknown"}, }; static int diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 2229ecd237..48f76c289c 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -106,6 +106,18 @@ select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= t (1 row) +select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); + _jsonpath_exists +------------------ + f +(1 row) + select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); _jsonpath_query ----------------- diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 4bd4e53b26..5326db9b8a 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -225,6 +225,12 @@ select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; $."g"?(@."x" >= @[*]?(@."a" > "abc")) (1 row) +select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; + jsonpath +--------------------------------------------- + $."g"?(("x" >= 123 || "a" == 4) is unknown) +(1 row) + select '$.g ? (zip == $zip)'::jsonpath; jsonpath ------------------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index d4086f243a..415c435314 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -17,6 +17,8 @@ select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @. select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); +select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 87f2336438..4f8ea98445 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -39,6 +39,7 @@ select '$.g ? (@.a == 1 || a == 4 && b == 7)'::jsonpath; select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; +select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; From 52809a866cc4f9deab0238b03579ab81e5566076 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 21:02:51 +0300 Subject: [PATCH 15/97] implement jsonpath EXISTS clause --- src/backend/utils/adt/jsonpath.c | 64 ++++++++++++++++---- src/backend/utils/adt/jsonpath_exec.c | 4 ++ src/backend/utils/adt/jsonpath_gram.y | 24 +++----- src/backend/utils/adt/jsonpath_scan.l | 6 +- src/include/utils/jsonpath.h | 4 +- src/test/regress/expected/jsonb_jsonpath.out | 17 ++++++ src/test/regress/expected/jsonpath.out | 24 ++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 4 ++ src/test/regress/sql/jsonpath.sql | 4 ++ 9 files changed, 117 insertions(+), 34 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c3fd3e14c4..3029df886e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -20,7 +20,8 @@ /*****************************INPUT/OUTPUT************************************/ static int -flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) +flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, + bool forbiddenRoot) { int32 pos = buf->len - VARHDRSZ; /* position from begining of jsonpath data */ int32 chld, next; @@ -35,17 +36,30 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) switch(item->type) { - case jpiKey: case jpiString: case jpiVariable: + /* scalars aren't checked during grammar parse */ + if (item->next != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("scalar could not be a part of path"))); + case jpiKey: appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len)); appendBinaryStringInfo(buf, item->string.val, item->string.len); appendStringInfoChar(buf, '\0'); break; case jpiNumeric: + if (item->next != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("scalar could not be a part of path"))); appendBinaryStringInfo(buf, (char*)item->numeric, VARSIZE(item->numeric)); break; case jpiBool: + if (item->next != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("scalar could not be a part of path"))); appendBinaryStringInfo(buf, (char*)&item->boolean, sizeof(item->boolean)); break; case jpiAnd: @@ -69,32 +83,50 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); - chld = flattenJsonPathParseItem(buf, item->args.left); + chld = flattenJsonPathParseItem(buf, item->args.left, forbiddenRoot); *(int32*)(buf->data + left) = chld; - chld = flattenJsonPathParseItem(buf, item->args.right); + chld = flattenJsonPathParseItem(buf, item->args.right, forbiddenRoot); *(int32*)(buf->data + right) = chld; } break; - case jpiNot: + case jpiFilter: case jpiIsUnknown: + case jpiNot: case jpiPlus: case jpiMinus: - case jpiFilter: + case jpiExists: { int32 arg; arg = buf->len; appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); - chld = flattenJsonPathParseItem(buf, item->arg); + chld = flattenJsonPathParseItem(buf, item->arg, + item->type == jpiFilter || + forbiddenRoot); *(int32*)(buf->data + arg) = chld; } break; + case jpiNull: + if (item->next != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("scalar could not be a part of path"))); + break; + case jpiRoot: + if (forbiddenRoot) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("root is not allowed in expression"))); + break; case jpiAnyArray: case jpiAnyKey: + break; case jpiCurrent: - case jpiRoot: - case jpiNull: + if (!forbiddenRoot) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("@ is not allowed in root expressions"))); break; case jpiIndexArray: appendBinaryStringInfo(buf, @@ -117,7 +149,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) } if (item->next) - *(int32*)(buf->data + next) = flattenJsonPathParseItem(buf, item->next); + *(int32*)(buf->data + next) = + flattenJsonPathParseItem(buf, item->next, forbiddenRoot); return pos; } @@ -141,7 +174,7 @@ jsonpath_in(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for jsonpath: \"%s\"", in))); - flattenJsonPathParseItem(&buf, jsonpath); + flattenJsonPathParseItem(&buf, jsonpath, false); res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); @@ -294,6 +327,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket printJsonPathItem(buf, &elem, false, false); appendBinaryStringInfo(buf, ") is unknown", 12); break; + case jpiExists: + appendBinaryStringInfo(buf,"exists (", 8); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + appendStringInfoChar(buf, ')'); + break; case jpiCurrent: Assert(!inKey); appendStringInfoChar(buf, '@'); @@ -435,6 +474,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32(v->args.right, base, pos); break; case jpiNot: + case jpiExists: case jpiIsUnknown: case jpiMinus: case jpiPlus: @@ -461,6 +501,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiFilter || v->type == jpiNot || v->type == jpiIsUnknown || + v->type == jpiExists || v->type == jpiPlus || v->type == jpiMinus ); @@ -481,6 +522,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiIndexArray || v->type == jpiFilter || v->type == jpiCurrent || + v->type == jpiExists || v->type == jpiRoot ); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 9aa4aa0f30..7d65c70111 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -738,6 +738,10 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) jsp->anybounds.last); break; } + case jpiExists: + jspGetArg(jsp, &elem); + res = recursiveExecute(&elem, vars, jb, NULL); + break; case jpiNull: case jpiBool: case jpiNumeric: diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index e5a4c2d1a7..be41118faf 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -207,7 +207,7 @@ makeAny(int first, int last) int optype; } -%token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P +%token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P @@ -215,7 +215,7 @@ makeAny(int first, int last) %type result jsonpath scalar_value path_primary expr array_accessor any_path accessor_op key unary_expr - predicate delimited_predicate // numeric + predicate delimited_predicate %type accessor_expr /* path absolute_path relative_path */ @@ -240,9 +240,6 @@ result: scalar_value: STRING_P { $$ = makeItemString(&$1); } - | TO_P { $$ = makeItemString(&$1); } - | IS_P { $$ = makeItemString(&$1); } - | UNKNOWN_P { $$ = makeItemString(&$1); } | NULL_P { $$ = makeItemString(NULL); } | TRUE_P { $$ = makeItemBool(true); } | FALSE_P { $$ = makeItemBool(false); } @@ -251,14 +248,6 @@ scalar_value: | VARIABLE_P { $$ = makeItemVariable(&$1); } ; -/* -numeric: - NUMERIC_P { $$ = makeItemNumeric(&$1); } - | INT_P { $$ = makeItemNumeric(&$1); } - | VARIABLE_P { $$ = makeItemVariable(&$1); } - ; -*/ - jsonpath: expr ; @@ -273,8 +262,8 @@ comp_op: ; delimited_predicate: - '(' predicate ')' { $$ = $2; } -// | EXISTS '(' relative_path ')' { $$ = makeItemUnary(jpiExists, $2); } + '(' predicate ')' { $$ = $2; } + | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); } ; predicate: @@ -309,8 +298,6 @@ unary_expr: | '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); } ; -// | '(' expr ')' { $$ = $2; } - expr: unary_expr { $$ = $1; } | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); } @@ -369,6 +356,9 @@ key: | NULL_P { $$ = makeItemKey(&$1); } | TRUE_P { $$ = makeItemKey(&$1); } | FALSE_P { $$ = makeItemKey(&$1); } + | IS_P { $$ = makeItemKey(&$1); } + | UNKNOWN_P { $$ = makeItemKey(&$1); } + | EXISTS_P { $$ = makeItemKey(&$1); } ; /* absolute_path: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 60a3031131..bed0d4fec4 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -172,7 +172,6 @@ unicode \\u[0-9A-Fa-f]{4} \/\* { yylval->str = scanstring; BEGIN xCOMMENT; - return checkSpecialVal(); } ({special}|\") { @@ -270,12 +269,13 @@ typedef struct keyword */ static keyword keywords[] = { - { 2, true, IS_P, "is"}, + { 2, false, IS_P, "is"}, { 2, false, TO_P, "to"}, { 4, true, NULL_P, "null"}, { 4, true, TRUE_P, "true"}, { 5, true, FALSE_P, "false"}, - { 7, true, UNKNOWN_P, "unknown"}, + { 6, false, EXISTS_P, "exists"}, + { 7, false, UNKNOWN_P, "unknown"} }; static int diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 31ed953b48..a106dd3178 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -55,14 +55,12 @@ typedef enum JsonPathItemType { jpiAnyKey, jpiIndexArray, jpiAny, - //jpiAll, - //jpiAllArray, - //jpiAllKey, jpiKey, jpiCurrent, jpiRoot, jpiVariable, jpiFilter, + jpiExists } JsonPathItemType; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 48f76c289c..826ce3fb83 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -448,3 +448,20 @@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ t (1 row) +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); + _jsonpath_query +----------------- + {"x": 2} +(1 row) + +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); + _jsonpath_query +----------------- + {"x": 2} +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 5326db9b8a..2599fcbbae 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -231,6 +231,30 @@ select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; $."g"?(("x" >= 123 || "a" == 4) is unknown) (1 row) +select '$.g ? (exists (.x))'::jsonpath; + jsonpath +------------------------ + $."g"?(exists (@."x")) +(1 row) + +select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath; + jsonpath +---------------------------------- + $."g"?(exists (@."x"?(@ == 14))) +(1 row) + +select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; + jsonpath +---------------------------------- + $."g"?(exists (@."x"?(@ == 14))) +(1 row) + +select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; + jsonpath +-------------------------------------------------------------- + $."g"?(("x" >= 123 || "a" == 4) && exists (@."x"?(@ == 14))) +(1 row) + select '$.g ? (zip == $zip)'::jsonpath; jsonpath ------------------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 415c435314..317705d02a 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -78,3 +78,7 @@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); + +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); +select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 4f8ea98445..6fcdffc7c5 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -40,6 +40,10 @@ select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; +select '$.g ? (exists (.x))'::jsonpath; +select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath; +select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; +select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; From 17ab6d570db499165ea3a4bcd77decd61d56a013 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 00:25:03 +0300 Subject: [PATCH 16/97] Fix jsonpath ternary logic --- src/backend/utils/adt/jsonpath_exec.c | 27 ++++++----- src/test/regress/expected/jsonb_jsonpath.out | 51 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 27 +++++++++++ 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 7d65c70111..8d51c6a53a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -462,19 +462,27 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) case jpiAnd: jspGetLeftArg(jsp, &elem); res = recursiveExecute(&elem, vars, jb, NULL); - if (res == jperOk) + if (res != jperNotFound) { + JsonPathExecResult res2; + jspGetRightArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res2 = recursiveExecute(&elem, vars, jb, NULL); + + res = res2 == jperOk ? res : res2; } break; case jpiOr: jspGetLeftArg(jsp, &elem); res = recursiveExecute(&elem, vars, jb, NULL); - if (res == jperNotFound) + if (res != jperOk) { + JsonPathExecResult res2; + jspGetRightArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res2 = recursiveExecute(&elem, vars, jb, NULL); + + res = res2 == jperNotFound ? res : res2; } break; case jpiNot: @@ -493,15 +501,8 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) break; case jpiIsUnknown: jspGetArg(jsp, &elem); - switch ((res = recursiveExecute(&elem, vars, jb, NULL))) - { - case jperError: - res = jperOk; - break; - default: - res = jperNotFound; - break; - } + res = recursiveExecute(&elem, vars, jb, NULL); + res = res == jperError ? jperOk : jperNotFound; break; case jpiKey: if (JsonbType(jb) == jbvObject) diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 826ce3fb83..fefd5907ed 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -465,3 +465,54 @@ select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ) {"x": 2} (1 row) +--test ternary logic +select + x, y, + _jsonpath_query( + jsonb '[true, false, null]', + '$[*] ? (@ == true && ($x == true && $y == true) || + @ == false && !($x == true && $y == true) || + @ == null && ($x == true && $y == true) is unknown)', + jsonb_build_object('x', x, 'y', y) + ) as "x && y" +from + (values (jsonb 'true'), ('false'), ('"null"')) x(x), + (values (jsonb '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, + _jsonpath_query( + jsonb '[true, false, null]', + '$[*] ? (@ == true && ($x == true || $y == true) || + @ == false && !($x == true || $y == true) || + @ == null && ($x == true || $y == true) is unknown)', + jsonb_build_object('x', x, 'y', y) + ) as "x || y" +from + (values (jsonb 'true'), ('false'), ('"null"')) x(x), + (values (jsonb '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) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 317705d02a..7d805a1d04 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -82,3 +82,30 @@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); + +--test ternary logic +select + x, y, + _jsonpath_query( + jsonb '[true, false, null]', + '$[*] ? (@ == true && ($x == true && $y == true) || + @ == false && !($x == true && $y == true) || + @ == null && ($x == true && $y == true) is unknown)', + jsonb_build_object('x', x, 'y', y) + ) as "x && y" +from + (values (jsonb 'true'), ('false'), ('"null"')) x(x), + (values (jsonb 'true'), ('false'), ('"null"')) y(y); + +select + x, y, + _jsonpath_query( + jsonb '[true, false, null]', + '$[*] ? (@ == true && ($x == true || $y == true) || + @ == false && !($x == true || $y == true) || + @ == null && ($x == true || $y == true) is unknown)', + jsonb_build_object('x', x, 'y', y) + ) as "x || y" +from + (values (jsonb 'true'), ('false'), ('"null"')) x(x), + (values (jsonb 'true'), ('false'), ('"null"')) y(y); From 9d761b658bb5c050a4ba6feed2e6fbc3492d8b43 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 01:29:41 +0300 Subject: [PATCH 17/97] Introduce struct JsonPathExecContext --- src/backend/utils/adt/jsonpath_exec.c | 84 +++++++++++++++------------ 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 8d51c6a53a..fd79460b2b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -19,8 +19,15 @@ #include "utils/json.h" #include "utils/jsonpath.h" -static JsonPathExecResult -recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found); +typedef struct JsonPathExecContext +{ + List *vars; + bool lax; +} JsonPathExecContext; + +static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, + List **found); /********************Execute functions for JsonPath***************************/ @@ -129,7 +136,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) } static void -computeJsonPathItem(JsonPathItem *item, List *vars, JsonbValue *value) +computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value) { switch(item->type) { @@ -149,7 +156,7 @@ computeJsonPathItem(JsonPathItem *item, List *vars, JsonbValue *value) value->val.string.val = jspGetString(item, &value->val.string.len); break; case jpiVariable: - computeJsonPathVariable(item, vars, value); + computeJsonPathVariable(item, cxt->vars, value); break; default: elog(ERROR, "Wrong type"); @@ -305,7 +312,7 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) } static JsonPathExecResult -executeExpr(JsonPathItem *jsp, List *vars, JsonbValue *jb) +executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { JsonPathExecResult res; JsonPathItem elem; @@ -313,17 +320,16 @@ executeExpr(JsonPathItem *jsp, List *vars, JsonbValue *jb) List *rseq = NIL; ListCell *llc; ListCell *rlc; - bool strict = true; /* FIXME pass */ bool error = false; bool found = false; jspGetLeftArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, &lseq); + res = recursiveExecute(cxt, &elem, jb, &lseq); if (res != jperOk) return res; jspGetRightArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, &rseq); + res = recursiveExecute(cxt, &elem, jb, &rseq); if (res != jperOk) return res; @@ -355,14 +361,14 @@ executeExpr(JsonPathItem *jsp, List *vars, JsonbValue *jb) if (res == jperOk) { - if (!strict) + if (cxt->lax) return jperOk; found = true; } else if (res == jperError) { - if (strict) + if (!cxt->lax) return jperError; error = true; @@ -390,10 +396,7 @@ copyJsonbValue(JsonbValue *src) } static JsonPathExecResult -recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found); - -static JsonPathExecResult -recursiveAny(JsonPathItem *jsp, List *vars, JsonbValue *jb, +recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found, uint32 level, uint32 first, uint32 last) { JsonPathExecResult res = jperNotFound; @@ -424,7 +427,7 @@ recursiveAny(JsonPathItem *jsp, List *vars, JsonbValue *jb, /* check expression */ if (jsp) { - res = recursiveExecute(jsp, vars, &v, found); + res = recursiveExecute(cxt, jsp, &v, found); if (res == jperOk && !found) break; } @@ -439,7 +442,7 @@ recursiveAny(JsonPathItem *jsp, List *vars, JsonbValue *jb, if (level < last && v.type == jbvBinary) { - res = recursiveAny(jsp, vars, &v, found, level + 1, first, last); + res = recursiveAny(cxt, jsp, &v, found, level + 1, first, last); if (res == jperOk && found == NULL) break; @@ -451,7 +454,8 @@ recursiveAny(JsonPathItem *jsp, List *vars, JsonbValue *jb, } static JsonPathExecResult -recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) +recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + List **found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -461,33 +465,33 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) switch(jsp->type) { case jpiAnd: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(cxt, &elem, jb, NULL); if (res != jperNotFound) { JsonPathExecResult res2; jspGetRightArg(jsp, &elem); - res2 = recursiveExecute(&elem, vars, jb, NULL); + res2 = recursiveExecute(cxt, &elem, jb, NULL); res = res2 == jperOk ? res : res2; } break; case jpiOr: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(cxt, &elem, jb, NULL); if (res != jperOk) { JsonPathExecResult res2; jspGetRightArg(jsp, &elem); - res2 = recursiveExecute(&elem, vars, jb, NULL); + res2 = recursiveExecute(cxt, &elem, jb, NULL); res = res2 == jperNotFound ? res : res2; } break; case jpiNot: jspGetArg(jsp, &elem); - switch((res = recursiveExecute(&elem, vars, jb, NULL))) + switch((res = recursiveExecute(cxt, &elem, jb, NULL))) { case jperOk: res = jperNotFound; @@ -501,7 +505,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) break; case jpiIsUnknown: jspGetArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(cxt, &elem, jb, NULL); res = res == jperError ? jperOk : jperNotFound; break; case jpiKey: @@ -518,7 +522,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { if (jspGetNext(jsp, &elem)) { - res = recursiveExecute(&elem, vars, v, found); + res = recursiveExecute(cxt, &elem, v, found); pfree(v); } else @@ -555,11 +559,11 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) JsonbExtractScalar(jb->val.binary.data, &v); - res = recursiveExecute(&elem, vars, &v, found); + res = recursiveExecute(cxt, &elem, &v, found); } else { - res = recursiveExecute(&elem, vars, jb, found); + res = recursiveExecute(cxt, &elem, jb, found); } break; case jpiAnyArray: @@ -579,7 +583,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { if (hasNext == true) { - res = recursiveExecute(&elem, vars, &v, found); + res = recursiveExecute(cxt, &elem, &v, found); if (res == jperError) break; @@ -621,7 +625,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) if (hasNext == true) { - res = recursiveExecute(&elem, vars, v, found); + res = recursiveExecute(cxt, &elem, v, found); if (res == jperError || found == NULL) break; @@ -658,7 +662,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { if (hasNext == true) { - res = recursiveExecute(&elem, vars, &v, found); + res = recursiveExecute(cxt, &elem, &v, found); if (res == jperError) break; @@ -685,12 +689,12 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: - res = executeExpr(jsp, vars, jb); + res = executeExpr(cxt, jsp, jb); break; case jpiRoot: if (jspGetNext(jsp, &elem)) { - res = recursiveExecute(&elem, vars, jb, found); + res = recursiveExecute(cxt, &elem, jb, found); } else { @@ -702,11 +706,11 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) break; case jpiFilter: jspGetArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(cxt, &elem, jb, NULL); if (res != jperOk) res = jperNotFound; else if (jspGetNext(jsp, &elem)) - res = recursiveExecute(&elem, vars, jb, found); + res = recursiveExecute(cxt, &elem, jb, found); else if (found) *found = lappend(*found, copyJsonbValue(jb)); break; @@ -719,7 +723,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) { if (hasNext) { - res = recursiveExecute(&elem, vars, jb, found); + res = recursiveExecute(cxt, &elem, jb, found); if (res == jperOk && !found) break; } @@ -733,7 +737,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) } if (jb->type == jbvBinary) - res = recursiveAny(hasNext ? &elem : NULL, vars, jb, found, + res = recursiveAny(cxt, hasNext ? &elem : NULL, jb, found, 1, jsp->anybounds.first, jsp->anybounds.last); @@ -741,7 +745,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) } case jpiExists: jspGetArg(jsp, &elem); - res = recursiveExecute(&elem, vars, jb, NULL); + res = recursiveExecute(cxt, &elem, jb, NULL); break; case jpiNull: case jpiBool: @@ -752,7 +756,7 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) if (found) { JsonbValue *jbv = palloc(sizeof(*jbv)); - computeJsonPathItem(jsp, vars, jbv); + computeJsonPathItem(cxt, jsp, jbv); *found = lappend(*found, jbv); } break; @@ -766,16 +770,20 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found) JsonPathExecResult executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) { + JsonPathExecContext cxt; JsonPathItem jsp; JsonbValue jbv; + cxt.vars = vars; + cxt.lax = false; /* FIXME */ + jbv.type = jbvBinary; jbv.val.binary.data = &json->root; jbv.val.binary.len = VARSIZE_ANY_EXHDR(json); jspInit(&jsp, path); - return recursiveExecute(&jsp, vars, &jbv, foundJson); + return recursiveExecute(&cxt, &jsp, &jbv, foundJson); } static Datum From 1daaf58c0d3f312a2b288e85616e4d5c74926da0 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 13:08:08 +0300 Subject: [PATCH 18/97] Add jsonpath binary arithmetic expression evaluation --- src/backend/utils/adt/jsonpath_exec.c | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index fd79460b2b..72019001f5 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -385,6 +385,84 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) return jperNotFound; } +static JsonPathExecResult +executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + List **found) +{ + JsonPathExecResult jper; + JsonPathItem elem; + List *lseq = NIL; + List *rseq = NIL; + JsonbValue *lval; + JsonbValue *rval; + JsonbValue lvalbuf; + JsonbValue rvalbuf; + Datum ldatum; + Datum rdatum; + Datum res; + + jspGetLeftArg(jsp, &elem); + jper = recursiveExecute(cxt, &elem, jb, &lseq); + if (jper == jperOk) + { + jspGetRightArg(jsp, &elem); + jper = recursiveExecute(cxt, &elem, jb, &rseq); + } + + if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + + lval = linitial(lseq); + rval = linitial(rseq); + + if (JsonbType(lval) == jbvScalar) + lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf); + + if (lval->type != jbvNumeric) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + + if (JsonbType(rval) == jbvScalar) + rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); + + if (rval->type != jbvNumeric) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + + if (!found) + return jperOk; + + ldatum = NumericGetDatum(lval->val.numeric); + rdatum = NumericGetDatum(rval->val.numeric); + + switch (jsp->type) + { + case jpiAdd: + res = DirectFunctionCall2(numeric_add, ldatum, rdatum); + break; + case jpiSub: + res = DirectFunctionCall2(numeric_sub, ldatum, rdatum); + break; + case jpiMul: + res = DirectFunctionCall2(numeric_mul, ldatum, rdatum); + break; + case jpiDiv: + res = DirectFunctionCall2(numeric_div, ldatum, rdatum); + break; + case jpiMod: + res = DirectFunctionCall2(numeric_mod, ldatum, rdatum); + break; + default: + elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); + } + + lval = palloc(sizeof(*lval)); + lval->type = jbvNumeric; + lval->val.numeric = DatumGetNumeric(res); + + *found = lappend(*found, lval); + + return jperOk; +} + static JsonbValue* copyJsonbValue(JsonbValue *src) { @@ -691,6 +769,13 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiGreaterOrEqual: res = executeExpr(cxt, jsp, jb); break; + case jpiAdd: + case jpiSub: + case jpiMul: + case jpiDiv: + case jpiMod: + res = executeArithmExpr(cxt, jsp, jb, found); + break; case jpiRoot: if (jspGetNext(jsp, &elem)) { From 2ec80384f74d3ad473915caff90bc5e90b0c96c6 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 12:18:30 +0300 Subject: [PATCH 19/97] improve tests --- src/test/regress/expected/jsonb_jsonpath.out | 36 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 7 ++++ 2 files changed, 43 insertions(+) diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index fefd5907ed..6db469a8fb 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -516,3 +516,39 @@ from "null" | "null" | null (9 rows) +select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$ ? (.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$ ? (.a == .b)'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 7d805a1d04..556d168132 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -109,3 +109,10 @@ select from (values (jsonb 'true'), ('false'), ('"null"')) x(x), (values (jsonb 'true'), ('false'), ('"null"')) y(y); + +select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$ ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$ ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); From e5017fe6b2e18d977c40a7e1d9d4765ac4b2f36a Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 13:17:02 +0300 Subject: [PATCH 20/97] fix support () in jsonpath expression --- src/backend/utils/adt/jsonpath_exec.c | 2 +- src/backend/utils/adt/jsonpath_gram.y | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 72019001f5..6215460740 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -215,7 +215,7 @@ checkScalarEquality(JsonbValue *jb1, JsonbValue *jb2) case jbvNumeric: return (compareNumeric(jb1->val.numeric, jb2->val.numeric) == 0); default: - elog(ERROR,"Wrong state"); + elog(ERROR,"1Wrong state"); return false; } } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index be41118faf..eb8923dbd0 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -213,7 +213,7 @@ makeAny(int first, int last) %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P -%type result jsonpath scalar_value path_primary expr +%type result jsonpath scalar_value path_primary expr pexpr array_accessor any_path accessor_op key unary_expr predicate delimited_predicate @@ -268,7 +268,7 @@ delimited_predicate: predicate: delimited_predicate { $$ = $1; } - | expr comp_op expr { $$ = makeItemBinary($2, $1, $3); } + | pexpr comp_op pexpr { $$ = makeItemBinary($2, $1, $3); } // | expr LIKE_REGEX pattern { $$ = ...; } // | expr STARTS WITH STRING_P { $$ = ...; } // | expr STARTS WITH '$' STRING_P { $$ = ...; } @@ -298,13 +298,18 @@ unary_expr: | '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); } ; +pexpr: + expr { $$ = $1; } + | '(' expr ')' { $$ = $2; } + ; + expr: unary_expr { $$ = $1; } - | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); } - | expr '-' expr { $$ = makeItemBinary(jpiSub, $1, $3); } - | expr '*' expr { $$ = makeItemBinary(jpiMul, $1, $3); } - | expr '/' expr { $$ = makeItemBinary(jpiDiv, $1, $3); } - | expr '%' expr { $$ = makeItemBinary(jpiMod, $1, $3); } + | pexpr '+' pexpr { $$ = makeItemBinary(jpiAdd, $1, $3); } + | pexpr '-' pexpr { $$ = makeItemBinary(jpiSub, $1, $3); } + | pexpr '*' pexpr { $$ = makeItemBinary(jpiMul, $1, $3); } + | pexpr '/' pexpr { $$ = makeItemBinary(jpiDiv, $1, $3); } + | pexpr '%' pexpr { $$ = makeItemBinary(jpiMod, $1, $3); } ; index_elem: From bf472e2950c8fca5e409e1dadae4a57974267f37 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 13:45:43 +0300 Subject: [PATCH 21/97] correct work of unnary plus/minus --- src/backend/utils/adt/jsonpath.c | 3 - src/backend/utils/adt/jsonpath_exec.c | 42 ++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 13 ++-- src/test/regress/expected/jsonb_jsonpath.out | 66 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 12 ++++ 5 files changed, 115 insertions(+), 21 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 3029df886e..c7b1ceda4e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -92,7 +92,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFilter: case jpiIsUnknown: case jpiNot: - case jpiPlus: case jpiMinus: case jpiExists: { @@ -477,7 +476,6 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiExists: case jpiIsUnknown: case jpiMinus: - case jpiPlus: case jpiFilter: read_int32(v->arg, base, pos); break; @@ -502,7 +500,6 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiNot || v->type == jpiIsUnknown || v->type == jpiExists || - v->type == jpiPlus || v->type == jpiMinus ); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 6215460740..514fddbd16 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -401,19 +401,31 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, Datum rdatum; Datum res; - jspGetLeftArg(jsp, &elem); + if (jsp->type == jpiMinus) + jspGetArg(jsp, &elem); + else + jspGetLeftArg(jsp, &elem); + jper = recursiveExecute(cxt, &elem, jb, &lseq); - if (jper == jperOk) + + if (jper == jperOk && jsp->type != jpiMinus) { jspGetRightArg(jsp, &elem); jper = recursiveExecute(cxt, &elem, jb, &rseq); } - if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + if (jsp->type == jpiMinus) + { + if (jper != jperOk || list_length(lseq) != 1) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + } + else + { + if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + } lval = linitial(lseq); - rval = linitial(rseq); if (JsonbType(lval) == jbvScalar) lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf); @@ -421,20 +433,29 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (lval->type != jbvNumeric) return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ - if (JsonbType(rval) == jbvScalar) - rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); + if (jsp->type != jpiMinus) + { + rval = linitial(rseq); - if (rval->type != jbvNumeric) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + if (JsonbType(rval) == jbvScalar) + rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); + + if (rval->type != jbvNumeric) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + } if (!found) return jperOk; ldatum = NumericGetDatum(lval->val.numeric); - rdatum = NumericGetDatum(rval->val.numeric); + if (jsp->type != jpiMinus) + rdatum = NumericGetDatum(rval->val.numeric); switch (jsp->type) { + case jpiMinus: + res = DirectFunctionCall1(numeric_uminus, ldatum); + break; case jpiAdd: res = DirectFunctionCall2(numeric_add, ldatum, rdatum); break; @@ -774,6 +795,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiMul: case jpiDiv: case jpiMod: + case jpiMinus: res = executeArithmExpr(cxt, jsp, jb, found); break; case jpiRoot: diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index eb8923dbd0..f9fb568939 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -214,7 +214,7 @@ makeAny(int first, int last) %token ANY_P %type result jsonpath scalar_value path_primary expr pexpr - array_accessor any_path accessor_op key unary_expr + array_accessor any_path accessor_op key predicate delimited_predicate %type accessor_expr /* path absolute_path relative_path */ @@ -228,6 +228,7 @@ makeAny(int first, int last) %right NOT_P %left '+' '-' %left '*' '/' '%' +%left UMINUS %nonassoc '(' ')' /* Grammar follows */ @@ -292,19 +293,15 @@ accessor_expr: | accessor_expr accessor_op { $$ = lappend($1, $2); } ; -unary_expr: - accessor_expr { $$ = makeItemList($1); } - | '+' unary_expr { $$ = makeItemUnary(jpiPlus, $2); } - | '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); } - ; - pexpr: expr { $$ = $1; } | '(' expr ')' { $$ = $2; } ; expr: - unary_expr { $$ = $1; } + accessor_expr { $$ = makeItemList($1); } + | '+' pexpr %prec UMINUS { $$ = $2; } + | '-' pexpr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); } | pexpr '+' pexpr { $$ = makeItemBinary(jpiAdd, $1, $3); } | pexpr '-' pexpr { $$ = makeItemBinary(jpiSub, $1, $3); } | pexpr '*' pexpr { $$ = makeItemBinary(jpiMul, $1, $3); } diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 6db469a8fb..8067f5f0a8 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -552,3 +552,69 @@ select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); t (1 row) +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)'); + _jsonpath_query +------------------ + {"a": 2, "b": 1} +(1 row) + +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))'); + _jsonpath_query +------------------ + {"a": 2, "b": 1} +(1 row) + +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)'); + _jsonpath_query +------------------ + {"a": 2, "b": 1} +(1 row) + +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (.b + 1))'); + _jsonpath_query +------------------ + {"a": 2, "b": 1} +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - 1)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -1)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -.b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 - - .b)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)'); + _jsonpath_exists +------------------ + t +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 556d168132..fe5fe27c7c 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -116,3 +116,15 @@ select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); + +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)'); +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))'); +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)'); +select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (.b + 1))'); +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - 1)'); +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -1)'); +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -.b)'); +select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 - - .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)'); From 9665ff0e66a9ac433bfa4bfc0f8f92b6c22a9273 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 15:40:09 +0300 Subject: [PATCH 22/97] Fix unary arithmetic jsonpath expressions --- src/backend/utils/adt/jsonpath.c | 3 + src/backend/utils/adt/jsonpath_exec.c | 108 +++++++++++++------ src/backend/utils/adt/jsonpath_gram.y | 2 +- src/test/regress/expected/jsonb_jsonpath.out | 24 +++++ src/test/regress/sql/jsonb_jsonpath.sql | 4 + 5 files changed, 108 insertions(+), 33 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c7b1ceda4e..6edb9d2ea7 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -92,6 +92,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFilter: case jpiIsUnknown: case jpiNot: + case jpiPlus: case jpiMinus: case jpiExists: { @@ -475,6 +476,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiNot: case jpiExists: case jpiIsUnknown: + case jpiPlus: case jpiMinus: case jpiFilter: read_int32(v->arg, base, pos); @@ -500,6 +502,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiNot || v->type == jpiIsUnknown || v->type == jpiExists || + v->type == jpiPlus || v->type == jpiMinus ); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 514fddbd16..ff789161ba 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -386,8 +386,8 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) } static JsonPathExecResult -executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found) +executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, List **found) { JsonPathExecResult jper; JsonPathItem elem; @@ -401,29 +401,18 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, Datum rdatum; Datum res; - if (jsp->type == jpiMinus) - jspGetArg(jsp, &elem); - else - jspGetLeftArg(jsp, &elem); + jspGetLeftArg(jsp, &elem); jper = recursiveExecute(cxt, &elem, jb, &lseq); - if (jper == jperOk && jsp->type != jpiMinus) + if (jper == jperOk) { jspGetRightArg(jsp, &elem); jper = recursiveExecute(cxt, &elem, jb, &rseq); } - if (jsp->type == jpiMinus) - { - if (jper != jperOk || list_length(lseq) != 1) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ - } - else - { - if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ - } + if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ lval = linitial(lseq); @@ -433,29 +422,22 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (lval->type != jbvNumeric) return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ - if (jsp->type != jpiMinus) - { - rval = linitial(rseq); + rval = linitial(rseq); - if (JsonbType(rval) == jbvScalar) - rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); + if (JsonbType(rval) == jbvScalar) + rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); - if (rval->type != jbvNumeric) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ - } + if (rval->type != jbvNumeric) + return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ if (!found) return jperOk; ldatum = NumericGetDatum(lval->val.numeric); - if (jsp->type != jpiMinus) - rdatum = NumericGetDatum(rval->val.numeric); + rdatum = NumericGetDatum(rval->val.numeric); switch (jsp->type) { - case jpiMinus: - res = DirectFunctionCall1(numeric_uminus, ldatum); - break; case jpiAdd: res = DirectFunctionCall2(numeric_add, ldatum, rdatum); break; @@ -484,7 +466,7 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return jperOk; } -static JsonbValue* +static JsonbValue * copyJsonbValue(JsonbValue *src) { JsonbValue *dst = palloc(sizeof(*dst)); @@ -494,6 +476,65 @@ copyJsonbValue(JsonbValue *src) return dst; } +static JsonPathExecResult +executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, List **found) +{ + JsonPathExecResult jper; + JsonPathItem elem; + List *seq = NIL; + ListCell *lc; + + jspGetArg(jsp, &elem); + jper = recursiveExecute(cxt, &elem, jb, &seq); + + if (jper == jperError) + return jperError; /* ERRCODE_JSON_NUMBER_NOT_FOUND; */ + + jper = jperNotFound; + + foreach(lc, seq) + { + JsonbValue *val = lfirst(lc); + JsonbValue valbuf; + + if (JsonbType(val) == jbvScalar) + val = JsonbExtractScalar(val->val.binary.data, &valbuf); + + if (val->type == jbvNumeric) + { + jper = jperOk; + + if (!found) + return jper; + } + else if (!found) + continue; /* skip non-numerics processing */ + + if (val->type != jbvNumeric) + return jperError; /* ERRCODE_JSON_NUMBER_NOT_FOUND; */ + + val = copyJsonbValue(val); + + switch (jsp->type) + { + case jpiPlus: + break; + case jpiMinus: + val->val.numeric = + DatumGetNumeric(DirectFunctionCall1( + numeric_uminus, NumericGetDatum(val->val.numeric))); + break; + default: + elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); + } + + *found = lappend(*found, val); + } + + return jper; +} + static JsonPathExecResult recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found, uint32 level, uint32 first, uint32 last) @@ -795,8 +836,11 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiMul: case jpiDiv: case jpiMod: + res = executeBinaryArithmExpr(cxt, jsp, jb, found); + break; + case jpiPlus: case jpiMinus: - res = executeArithmExpr(cxt, jsp, jb, found); + res = executeUnaryArithmExpr(cxt, jsp, jb, found); break; case jpiRoot: if (jspGetNext(jsp, &elem)) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index f9fb568939..df065658a0 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -300,7 +300,7 @@ pexpr: expr: accessor_expr { $$ = makeItemList($1); } - | '+' pexpr %prec UMINUS { $$ = $2; } + | '+' pexpr %prec UMINUS { $$ = makeItemUnary(jpiPlus, $2); } | '-' pexpr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); } | pexpr '+' pexpr { $$ = makeItemBinary(jpiAdd, $1, $3); } | pexpr '-' pexpr { $$ = makeItemBinary(jpiSub, $1, $3); } diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 8067f5f0a8..abc04eba99 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -618,3 +618,27 @@ select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)' t (1 row) +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); + _jsonpath_exists +------------------ + f +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index fe5fe27c7c..4981e0ddd2 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -128,3 +128,7 @@ select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 - - .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)'); +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); +select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); From 3115cb751eeb9eb4c6c00befcad63474028e5d45 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 15:54:51 +0300 Subject: [PATCH 23/97] make beauty --- src/backend/utils/adt/jsonpath.c | 34 ++++++----- src/backend/utils/adt/jsonpath_gram.y | 82 ++++++++++----------------- src/include/utils/jsonpath.h | 54 +++++++++++------- 3 files changed, 85 insertions(+), 85 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 6edb9d2ea7..6d7de7450c 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -44,8 +44,9 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("scalar could not be a part of path"))); case jpiKey: - appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len)); - appendBinaryStringInfo(buf, item->string.val, item->string.len); + appendBinaryStringInfo(buf, (char*)&item->value.string.len, + sizeof(item->value.string.len)); + appendBinaryStringInfo(buf, item->value.string.val, item->value.string.len); appendStringInfoChar(buf, '\0'); break; case jpiNumeric: @@ -53,14 +54,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("scalar could not be a part of path"))); - appendBinaryStringInfo(buf, (char*)item->numeric, VARSIZE(item->numeric)); + appendBinaryStringInfo(buf, (char*)item->value.numeric, + VARSIZE(item->value.numeric)); break; case jpiBool: if (item->next != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("scalar could not be a part of path"))); - appendBinaryStringInfo(buf, (char*)&item->boolean, sizeof(item->boolean)); + appendBinaryStringInfo(buf, (char*)&item->value.boolean, + sizeof(item->value.boolean)); break; case jpiAnd: case jpiOr: @@ -83,9 +86,9 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); - chld = flattenJsonPathParseItem(buf, item->args.left, forbiddenRoot); + chld = flattenJsonPathParseItem(buf, item->value.args.left, forbiddenRoot); *(int32*)(buf->data + left) = chld; - chld = flattenJsonPathParseItem(buf, item->args.right, forbiddenRoot); + chld = flattenJsonPathParseItem(buf, item->value.args.right, forbiddenRoot); *(int32*)(buf->data + right) = chld; } break; @@ -101,7 +104,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, arg = buf->len; appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); - chld = flattenJsonPathParseItem(buf, item->arg, + chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || forbiddenRoot); *(int32*)(buf->data + arg) = chld; @@ -130,19 +133,20 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, break; case jpiIndexArray: appendBinaryStringInfo(buf, - (char*)&item->array.nelems, - sizeof(item->array.nelems)); + (char*)&item->value.array.nelems, + sizeof(item->value.array.nelems)); appendBinaryStringInfo(buf, - (char*)item->array.elems, - item->array.nelems * sizeof(item->array.elems[0])); + (char*)item->value.array.elems, + item->value.array.nelems * + sizeof(item->value.array.elems[0])); break; case jpiAny: appendBinaryStringInfo(buf, - (char*)&item->anybounds.first, - sizeof(item->anybounds.first)); + (char*)&item->value.anybounds.first, + sizeof(item->value.anybounds.first)); appendBinaryStringInfo(buf, - (char*)&item->anybounds.last, - sizeof(item->anybounds.last)); + (char*)&item->value.anybounds.last, + sizeof(item->value.anybounds.last)); break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index df065658a0..06d8cb7105 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -55,8 +55,8 @@ makeItemString(string *s) else { v = makeItemType(jpiString); - v->string.val = s->val; - v->string.len = s->len; + v->value.string.val = s->val; + v->value.string.len = s->len; } return v; @@ -68,8 +68,8 @@ makeItemVariable(string *s) JsonPathParseItem *v; v = makeItemType(jpiVariable); - v->string.val = s->val; - v->string.len = s->len; + v->value.string.val = s->val; + v->value.string.len = s->len; return v; } @@ -91,7 +91,8 @@ makeItemNumeric(string *s) JsonPathParseItem *v; v = makeItemType(jpiNumeric); - v->numeric = DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1)); + v->value.numeric = + DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1)); return v; } @@ -100,7 +101,7 @@ static JsonPathParseItem* makeItemBool(bool val) { JsonPathParseItem *v = makeItemType(jpiBool); - v->boolean = val; + v->value.boolean = val; return v; } @@ -110,8 +111,8 @@ makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra) { JsonPathParseItem *v = makeItemType(type); - v->args.left = la; - v->args.right = ra; + v->value.args.left = la; + v->value.args.right = ra; return v; } @@ -127,15 +128,15 @@ makeItemUnary(int type, JsonPathParseItem* a) if (type == jpiMinus && a->type == jpiNumeric && !a->next) { v = makeItemType(jpiNumeric); - v->numeric = + v->value.numeric = DatumGetNumeric(DirectFunctionCall1(numeric_uminus, - NumericGetDatum(a->numeric))); + NumericGetDatum(a->value.numeric))); return v; } v = makeItemType(type); - v->arg = a; + v->value.arg = a; return v; } @@ -169,12 +170,12 @@ makeIndexArray(List *list) int i = 0; Assert(list_length(list) > 0); - v->array.nelems = list_length(list); + v->value.array.nelems = list_length(list); - v->array.elems = palloc(sizeof(v->array.elems[0]) * v->array.nelems); + v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems); foreach(cell, list) - v->array.elems[i++] = lfirst_int(cell); + v->value.array.elems[i++] = lfirst_int(cell); return v; } @@ -184,8 +185,8 @@ makeAny(int first, int last) { JsonPathParseItem *v = makeItemType(jpiAny); - v->anybounds.first = (first > 0) ? first : 0; - v->anybounds.last = (last >= 0) ? last : PG_UINT32_MAX; + v->value.anybounds.first = (first > 0) ? first : 0; + v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX; return v; } @@ -202,7 +203,7 @@ makeAny(int first, int last) %union { string str; List *elems; /* list of JsonPathParseItem */ - List *indexs; + List *indexs; /* list of integers */ JsonPathParseItem *value; int optype; } @@ -213,11 +214,10 @@ makeAny(int first, int last) %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P -%type result jsonpath scalar_value path_primary expr pexpr - array_accessor any_path accessor_op key - predicate delimited_predicate +%type result scalar_value path_primary expr pexpr array_accessor + any_path accessor_op key predicate delimited_predicate -%type accessor_expr /* path absolute_path relative_path */ +%type accessor_expr %type index_elem index_list @@ -235,7 +235,7 @@ makeAny(int first, int last) %% result: - jsonpath { *result = $1; } + expr { *result = $1; } | /* EMPTY */ { *result = NULL; } ; @@ -249,10 +249,6 @@ scalar_value: | VARIABLE_P { $$ = makeItemVariable(&$1); } ; -jsonpath: - expr - ; - comp_op: EQUAL_P { $$ = jpiEqual; } | NOTEQUAL_P { $$ = jpiNotEqual; } @@ -269,16 +265,18 @@ delimited_predicate: predicate: delimited_predicate { $$ = $1; } - | pexpr comp_op pexpr { $$ = makeItemBinary($2, $1, $3); } -// | expr LIKE_REGEX pattern { $$ = ...; } -// | expr STARTS WITH STRING_P { $$ = ...; } -// | expr STARTS WITH '$' STRING_P { $$ = ...; } -// | expr STARTS WITH '$' STRING_P { $$ = ...; } -// | '.' any_key right_expr { $$ = makeItemList(list_make2($2, $3)); } - | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } + | pexpr comp_op pexpr { $$ = makeItemBinary($2, $1, $3); } | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); } | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); } | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); } + | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } +/* + Left for the future + | pexpr LIKE_REGEX pattern { $$ = ...; } + | pexpr STARTS WITH STRING_P { $$ = ...; } + | pexpr STARTS WITH '$' STRING_P { $$ = ...; } + | pexpr STARTS WITH '$' STRING_P { $$ = ...; } +*/ ; path_primary: @@ -362,23 +360,5 @@ key: | UNKNOWN_P { $$ = makeItemKey(&$1); } | EXISTS_P { $$ = makeItemKey(&$1); } ; -/* -absolute_path: - '$' { $$ = list_make1(makeItemType(jpiRoot)); } - | '$' path { $$ = lcons(makeItemType(jpiRoot), $2); } - ; - -relative_path: - key { $$ = list_make1(makeItemType(jpiCurrent), $1); } - | key path { $$ = lcons(makeItemType(jpiCurrent), lcons($1, $2)); } - | '@' { $$ = list_make1(makeItemType(jpiCurrent)); } - | '@' path { $$ = lcons(makeItemType(jpiCurrent), $2); } - ; - -path: - accessor_op { $$ = list_make($1); } - | path accessor_op { $$ = lappend($1, $2); } - ; -*/ %% diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index a106dd3178..8874c86900 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -20,15 +20,22 @@ typedef struct { - int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 vl_len_;/* varlena header (do not touch directly!) */ + uint32 header; /* just version, other bits are reservedfor future use */ + char data[FLEXIBLE_ARRAY_MEMBER]; } JsonPath; +#define JSONPATH_VERSION (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)) #define PG_GETARG_JSONPATH_P_COPY(x) DatumGetJsonPathPCopy(PG_GETARG_DATUM(x)) #define PG_RETURN_JSONPATH_P(p) PG_RETURN_POINTER(p) +/* + * All node's type of jsonpath expression + */ typedef enum JsonPathItemType { jpiNull = jbvNull, jpiString = jbvString, @@ -69,8 +76,7 @@ typedef enum JsonPathItemType { * Unlike many other representation of expression the first/main * node is not an operation but left operand of expression. That * allows to implement cheep follow-path descending in jsonb - * structure and then execute operator with right operand which - * is always a constant. + * structure and then execute operator with right operand */ typedef struct JsonPathItem { @@ -79,27 +85,31 @@ typedef struct JsonPathItem { char *base; union { - struct { - char *data; /* for bool, numeric and string/key */ - int32 datalen; /* filled only for string/key */ - } value; - + /* classic operator with two operands: and, or etc */ struct { int32 left; int32 right; } args; + /* any unary operation */ int32 arg; + /* storage for jpiIndexArray: indexes of array */ struct { - int32 nelems; + int32 nelems; int32 *elems; } array; + /* jpiAny: levels */ struct { uint32 first; uint32 last; } anybounds; + + struct { + char *data; /* for bool, numeric and string/key */ + int32 datalen; /* filled only for string/key */ + } value; }; } JsonPathItem; @@ -124,36 +134,42 @@ struct JsonPathParseItem { JsonPathParseItem *next; /* next in path */ union { + + /* classic operator with two operands: and, or etc */ struct { JsonPathParseItem *left; JsonPathParseItem *right; } args; + /* any unary operation */ JsonPathParseItem *arg; - Numeric numeric; - bool boolean; - struct { - uint32 len; - char *val; /* could not be not null-terminated */ - } string; - + /* storage for jpiIndexArray: indexes of array */ struct { int nelems; int32 *elems; } array; + /* jpiAny: levels */ struct { uint32 first; uint32 last; } anybounds; - }; + + /* scalars */ + Numeric numeric; + bool boolean; + struct { + uint32 len; + char *val; /* could not be not null-terminated */ + } string; + } value; }; extern JsonPathParseItem* parsejsonpath(const char *str, int len); /* - * Execution + * Evaluation of jsonpath */ typedef enum JsonPathExecResult { @@ -168,7 +184,7 @@ typedef Datum (*JsonPathVariable_cb)(void *, bool *); typedef struct JsonPathVariable { text *varName; Oid typid; - int32 typmod; /* do we need it here? */ + int32 typmod; JsonPathVariable_cb cb; void *cb_arg; } JsonPathVariable; From 564c9bc7f30144288e1bb1241b076b4fdc4c0d4f Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 16:26:59 +0300 Subject: [PATCH 24/97] add header for jsonpath type --- src/backend/utils/adt/jsonpath.c | 9 ++++++--- src/include/utils/jsonpath.h | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 6d7de7450c..97d106f5c9 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -23,7 +23,8 @@ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, bool forbiddenRoot) { - int32 pos = buf->len - VARHDRSZ; /* position from begining of jsonpath data */ + /* position from begining of jsonpath data */ + int32 pos = buf->len - JSONPATH_HDRSZ; int32 chld, next; check_stack_depth(); @@ -171,7 +172,7 @@ jsonpath_in(PG_FUNCTION_ARGS) initStringInfo(&buf); enlargeStringInfo(&buf, 4 * len /* estimation */); - appendStringInfoSpaces(&buf, VARHDRSZ); + appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); if (!jsonpath) ereport(ERROR, @@ -182,6 +183,7 @@ jsonpath_in(PG_FUNCTION_ARGS) res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); + res->header = JSONPATH_VERSION; PG_RETURN_JSONPATH_P(res); } @@ -424,7 +426,8 @@ jsonpath_out(PG_FUNCTION_ARGS) void jspInit(JsonPathItem *v, JsonPath *js) { - jspInitByBuffer(v, VARDATA(js), 0); + Assert(js->header == JSONPATH_VERSION); + jspInitByBuffer(v, js->data, 0); } void diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 8874c86900..e2222b7dc0 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -26,6 +26,7 @@ typedef struct } JsonPath; #define JSONPATH_VERSION (0x01) +#define JSONPATH_HDRSZ (offsetof(JsonPath, data)) #define DatumGetJsonPathP(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM(d))) #define DatumGetJsonPathPCopy(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM_COPY(d))) From 071165f88f4f20f0d13cacc9b1680d44403859f0 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 16:40:39 +0300 Subject: [PATCH 25/97] completely remove anon union in jsonpath --- src/backend/utils/adt/jsonpath.c | 56 +++++++++++++-------------- src/backend/utils/adt/jsonpath_exec.c | 10 ++--- src/include/utils/jsonpath.h | 9 ++++- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 97d106f5c9..c3c16f4d94 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -357,11 +357,11 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket break; case jpiIndexArray: appendStringInfoChar(buf, '['); - for(i = 0; i< v->array.nelems; i++) + for(i = 0; i< v->content.array.nelems; i++) { if (i) appendStringInfoChar(buf, ','); - appendStringInfo(buf, "%d", v->array.elems[i]); + appendStringInfo(buf, "%d", v->content.array.elems[i]); } appendStringInfoChar(buf, ']'); break; @@ -369,18 +369,18 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket if (inKey) appendStringInfoChar(buf, '.'); - if (v->anybounds.first == 0 && - v->anybounds.last == PG_UINT32_MAX) + if (v->content.anybounds.first == 0 && + v->content.anybounds.last == PG_UINT32_MAX) appendBinaryStringInfo(buf, "**", 2); - else if (v->anybounds.first == 0) - appendStringInfo(buf, "**{,%u}", v->anybounds.last); - else if (v->anybounds.last == PG_UINT32_MAX) - appendStringInfo(buf, "**{%u,}", v->anybounds.first); - else if (v->anybounds.first == v->anybounds.last) - appendStringInfo(buf, "**{%u}", v->anybounds.first); + else if (v->content.anybounds.first == 0) + appendStringInfo(buf, "**{,%u}", v->content.anybounds.last); + else if (v->content.anybounds.last == PG_UINT32_MAX) + appendStringInfo(buf, "**{%u,}", v->content.anybounds.first); + else if (v->content.anybounds.first == v->content.anybounds.last) + appendStringInfo(buf, "**{%u}", v->content.anybounds.first); else - appendStringInfo(buf, "**{%u,%u}", v->anybounds.first, - v->anybounds.last); + appendStringInfo(buf, "**{%u,%u}", v->content.anybounds.first, + v->content.anybounds.last); break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); @@ -458,11 +458,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiKey: case jpiString: case jpiVariable: - read_int32(v->value.datalen, base, pos); + read_int32(v->content.value.datalen, base, pos); /* follow next */ case jpiNumeric: case jpiBool: - v->value.data = base + pos; + v->content.value.data = base + pos; break; case jpiAnd: case jpiOr: @@ -477,8 +477,8 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: - read_int32(v->args.left, base, pos); - read_int32(v->args.right, base, pos); + read_int32(v->content.args.left, base, pos); + read_int32(v->content.args.right, base, pos); break; case jpiNot: case jpiExists: @@ -486,15 +486,15 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiPlus: case jpiMinus: case jpiFilter: - read_int32(v->arg, base, pos); + read_int32(v->content.arg, base, pos); break; case jpiIndexArray: - read_int32(v->array.nelems, base, pos); - read_int32_n(v->array.elems, base, pos, v->array.nelems); + read_int32(v->content.array.nelems, base, pos); + read_int32_n(v->content.array.elems, base, pos, v->content.array.nelems); break; case jpiAny: - read_int32(v->anybounds.first, base, pos); - read_int32(v->anybounds.last, base, pos); + read_int32(v->content.anybounds.first, base, pos); + read_int32(v->content.anybounds.last, base, pos); break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); @@ -513,7 +513,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMinus ); - jspInitByBuffer(a, v->base, v->arg); + jspInitByBuffer(a, v->base, v->content.arg); } bool @@ -560,7 +560,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMod ); - jspInitByBuffer(a, v->base, v->args.left); + jspInitByBuffer(a, v->base, v->content.args.left); } void @@ -582,7 +582,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMod ); - jspInitByBuffer(a, v->base, v->args.right); + jspInitByBuffer(a, v->base, v->content.args.right); } bool @@ -590,7 +590,7 @@ jspGetBool(JsonPathItem *v) { Assert(v->type == jpiBool); - return (bool)*v->value.data; + return (bool)*v->content.value.data; } Numeric @@ -598,7 +598,7 @@ jspGetNumeric(JsonPathItem *v) { Assert(v->type == jpiNumeric); - return (Numeric)v->value.data; + return (Numeric)v->content.value.data; } char* @@ -611,6 +611,6 @@ jspGetString(JsonPathItem *v, int32 *len) ); if (len) - *len = v->value.datalen; - return v->value.data; + *len = v->content.value.datalen; + return v->content.value.data; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index ff789161ba..975b706267 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -754,11 +754,11 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, hasNext = jspGetNext(jsp, &elem); - for(i=0; iarray.nelems; i++) + for(i=0; icontent.array.nelems; i++) { /* TODO for future: array index can be expression */ v = getIthJsonbValueFromContainer(jb->val.binary.data, - jsp->array.elems[i]); + jsp->content.array.elems[i]); if (v == NULL) continue; @@ -870,7 +870,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, bool hasNext = jspGetNext(jsp, &elem); /* first try without any intermediate steps */ - if (jsp->anybounds.first == 0) + if (jsp->content.anybounds.first == 0) { if (hasNext) { @@ -890,8 +890,8 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (jb->type == jbvBinary) res = recursiveAny(cxt, hasNext ? &elem : NULL, jb, found, 1, - jsp->anybounds.first, - jsp->anybounds.last); + jsp->content.anybounds.first, + jsp->content.anybounds.last); break; } case jpiExists: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index e2222b7dc0..18042ada5d 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -82,7 +82,14 @@ typedef enum JsonPathItemType { typedef struct JsonPathItem { JsonPathItemType type; + + /* position form base to next node */ int32 nextPos; + + /* + * pointer into JsonPath value to current node, all + * positions of current are relative to this base + */ char *base; union { @@ -111,7 +118,7 @@ typedef struct JsonPathItem { char *data; /* for bool, numeric and string/key */ int32 datalen; /* filled only for string/key */ } value; - }; + } content; } JsonPathItem; extern void jspInit(JsonPathItem *v, JsonPath *js); From 691f0af5289fee86e299814ddcf506deadf5f6d5 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 18:27:19 +0300 Subject: [PATCH 26/97] make beauty --- src/backend/utils/adt/jsonpath.c | 24 ++++++ src/backend/utils/adt/jsonpath_exec.c | 109 +++++++++++++++++--------- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index c3c16f4d94..7e1cefde31 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -19,6 +19,10 @@ #include "utils/jsonpath.h" /*****************************INPUT/OUTPUT************************************/ + +/* + * Convert AST to flat jsonpath type representation + */ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, bool forbiddenRoot) @@ -33,6 +37,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, alignStringInfoInt(buf); next = (item->next) ? buf->len : 0; + + /* + * actual value will be recorded later, after next and + * children processing + */ appendBinaryStringInfo(buf, (char*)&next /* fake value */, sizeof(next)); switch(item->type) @@ -83,6 +92,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 left, right; left = buf->len; + + /* + * first, reserve place for left/right arg's positions, then + * record both args and sets actual position in reserved places + */ appendBinaryStringInfo(buf, (char*)&left /* fake value */, sizeof(left)); right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); @@ -408,6 +422,10 @@ jsonpath_out(PG_FUNCTION_ARGS) /********************Support functions for JsonPath****************************/ +/* + * Support macroses to read stored values + */ + #define read_byte(v, b, p) do { \ (v) = *(uint8*)((b) + (p)); \ (p) += 1; \ @@ -423,6 +441,9 @@ jsonpath_out(PG_FUNCTION_ARGS) (p) += sizeof(int32) * (n); \ } while(0) \ +/* + * Read root node and fill root node representation + */ void jspInit(JsonPathItem *v, JsonPath *js) { @@ -430,6 +451,9 @@ jspInit(JsonPathItem *v, JsonPath *js) jspInitByBuffer(v, js->data, 0); } +/* + * Read node from buffer and fill its representation + */ void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) { diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 975b706267..40ad23fcbd 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -13,11 +13,13 @@ #include "postgres.h" #include "funcapi.h" #include "miscadmin.h" +#include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/jsonpath.h" +#include "utils/varlena.h" typedef struct JsonPathExecContext { @@ -31,6 +33,9 @@ static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, /********************Execute functions for JsonPath***************************/ +/* + * Find value of jsonpath variable in a list of passing params + */ static void computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) { @@ -135,6 +140,9 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) } } +/* + * Convert jsonpath's scalar or variable node to actual jsonb value + */ static void computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value) { @@ -164,6 +172,12 @@ computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *va } +/* + * Returns jbv* type of of JsonbValue. Note, it never returns + * jbvBinary as is - jbvBinary is used as mark of store naked + * scalar value. To improve readability it defines jbvScalar + * as alias to jbvBinary + */ #define jbvScalar jbvBinary static int JsonbType(JsonbValue *jb) @@ -199,31 +213,10 @@ compareNumeric(Numeric a, Numeric b) ); } -static bool -checkScalarEquality(JsonbValue *jb1, JsonbValue *jb2) -{ - switch (jb1->type) - { - case jbvNull: - return true; - case jbvString: - return (jb1->val.string.len == jb2->val.string.len && - memcmp(jb2->val.string.val, jb1->val.string.val, - jb1->val.string.len) == 0); - case jbvBool: - return (jb2->val.boolean == jb1->val.boolean); - case jbvNumeric: - return (compareNumeric(jb1->val.numeric, jb2->val.numeric) == 0); - default: - elog(ERROR,"1Wrong state"); - return false; - } -} - static JsonPathExecResult checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) { - bool eq; + bool eq = false; if (jb1->type != jb2->type) { @@ -235,14 +228,28 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) if (jb1->type == jbvBinary) return jperError; - /* - eq = compareJsonbContainers(jb1->val.binary.data, - jb2->val.binary.data) == 0; - */ - else - eq = checkScalarEquality(jb1, jb2); - return !!not ^ !!eq ? jperOk : jperNotFound; + switch (jb1->type) + { + case jbvNull: + eq = true; + break; + case jbvString: + eq = (jb1->val.string.len == jb2->val.string.len && + memcmp(jb2->val.string.val, jb1->val.string.val, + jb1->val.string.len) == 0); + break; + case jbvBool: + eq = (jb2->val.boolean == jb1->val.boolean); + break; + case jbvNumeric: + eq = (compareNumeric(jb1->val.numeric, jb2->val.numeric) == 0); + break; + default: + elog(ERROR,"1Wrong state"); + } + + return (not ^ eq) ? jperOk : jperNotFound; } static JsonPathExecResult @@ -272,13 +279,11 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) case jbvNumeric: cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric); break; - /* case jbvString: cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len, jb2->val.string.val, jb2->val.string.len, - collationId); + DEFAULT_COLLATION_OID); break; - */ default: return jperError; } @@ -535,6 +540,9 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return jper; } +/* + * implements jpiAny node (** operator) + */ static JsonPathExecResult recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found, uint32 level, uint32 first, uint32 last) @@ -551,6 +559,9 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, it = JsonbIteratorInit(jb->val.binary.data); + /* + * Recursively iterate over jsonb objects/arrays + */ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) { if (r == WJB_KEY) @@ -593,12 +604,23 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return res; } +/* + * Main executor function: walks on jsonpath structure and tries to find + * correspoding parts of jsonb. Note, jsonb and jsonpath values should be + * avaliable and untoasted during work because JsonPathItem, JsonbValue + * and found could have pointers into input values. If caller wants just to + * check matching of json by jsonpath then it doesn't provide a found arg. + * In this case executor works till first positive result and does not check + * the rest if it is possible. In other case it tries to find all satisfied + * results + */ static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; + bool hasNext; check_stack_depth(); @@ -610,10 +632,15 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, { JsonPathExecResult res2; + /* + * SQL/JSON says that we should check second arg + * in case of jperError + */ + jspGetRightArg(jsp, &elem); res2 = recursiveExecute(cxt, &elem, jb, NULL); - res = res2 == jperOk ? res : res2; + res = (res2 == jperOk) ? res : res2; } break; case jpiOr: @@ -626,7 +653,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, jspGetRightArg(jsp, &elem); res2 = recursiveExecute(cxt, &elem, jb, NULL); - res = res2 == jperNotFound ? res : res2; + res = (res2 == jperNotFound) ? res : res2; } break; case jpiNot: @@ -646,7 +673,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiIsUnknown: jspGetArg(jsp, &elem); res = recursiveExecute(cxt, &elem, jb, NULL); - res = res == jperError ? jperOk : jperNotFound; + res = (res == jperError) ? jperOk : jperNotFound; break; case jpiKey: if (JsonbType(jb) == jbvObject) @@ -679,6 +706,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiCurrent: if (!jspGetNext(jsp, &elem)) { + /* we are last in chain of node */ res = jperOk; if (found) { @@ -688,7 +716,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, v = JsonbExtractScalar(jb->val.binary.data, palloc(sizeof(*v))); else - v = copyJsonbValue(jb); /* FIXME */ + v = copyJsonbValue(jb); *found = lappend(*found, v); } @@ -712,7 +740,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonbIterator *it; int32 r; JsonbValue v; - bool hasNext; hasNext = jspGetNext(jsp, &elem); it = JsonbIteratorInit(jb->val.binary.data); @@ -749,7 +776,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (JsonbType(jb) == jbvArray) { JsonbValue *v; - bool hasNext; int i; hasNext = jspGetNext(jsp, &elem); @@ -791,7 +817,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonbIterator *it; int32 r; JsonbValue v; - bool hasNext; hasNext = jspGetNext(jsp, &elem); it = JsonbIteratorInit(jb->val.binary.data); @@ -918,6 +943,9 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return res; } +/* + * Public interface to jsonpath executor + */ JsonPathExecResult executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) { @@ -951,6 +979,9 @@ returnNULL(void *arg, bool *isNull) return Int32GetDatum(0); } +/* + * Convert jsonb object into list of vars for executor + */ static List* makePassingVars(Jsonb *jb) { From e72392d19c6bbcb266b0a0c64f2819ca00693ff5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:18:40 +0300 Subject: [PATCH 27/97] Add jsonpath JsonbInitBinary() --- src/backend/utils/adt/jsonpath_exec.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 40ad23fcbd..55817fa8cc 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -31,6 +31,16 @@ static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found); +static inline JsonbValue * +JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) +{ + jbv->type = jbvBinary; + jbv->val.binary.data = &jb->root; + jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb); + + return jbv; +} + /********************Execute functions for JsonPath***************************/ /* @@ -123,11 +133,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) if (JB_ROOT_IS_SCALAR(jb)) JsonbExtractScalar(&jb->root, value); else - { - value->type = jbvBinary; - value->val.binary.data = &jb->root; - value->val.binary.len = VARSIZE_ANY_EXHDR(jb); - } + JsonbInitBinary(value, jb); } break; case (Oid) -1: /* raw JsonbValue */ @@ -956,10 +962,8 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) cxt.vars = vars; cxt.lax = false; /* FIXME */ - jbv.type = jbvBinary; - jbv.val.binary.data = &json->root; - jbv.val.binary.len = VARSIZE_ANY_EXHDR(json); - + JsonbInitBinary(&jbv, json); + jspInit(&jsp, path); return recursiveExecute(&cxt, &jsp, &jbv, foundJson); From b4f085d92f55783fcaa85cd528ad28c5cb832c1d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:19:48 +0300 Subject: [PATCH 28/97] Add jsonpath JsonbWrapInBinary() --- src/backend/utils/adt/jsonpath_exec.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 55817fa8cc..a0dca7f077 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -41,6 +41,17 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } +static inline JsonbValue * +JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out) +{ + Jsonb *jb = JsonbValueToJsonb(jbv); + + if (!out) + out = palloc(sizeof(*out)); + + return JsonbInitBinary(out, jb); +} + /********************Execute functions for JsonPath***************************/ /* From 1b32aa2f55551aaa230c07b24852adaff0de603f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 04:03:41 +0300 Subject: [PATCH 29/97] Add STRICT/LAX jsonpath mode --- src/backend/utils/adt/jsonpath.c | 13 +- src/backend/utils/adt/jsonpath_exec.c | 351 ++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 26 +- src/backend/utils/adt/jsonpath_scan.l | 8 +- src/include/utils/jsonpath.h | 22 +- src/include/utils/jsonpath_scanner.h | 2 +- src/test/regress/expected/jsonb_jsonpath.out | 124 +++++-- src/test/regress/expected/jsonpath.out | 12 + src/test/regress/sql/jsonb_jsonpath.sql | 75 ++-- src/test/regress/sql/jsonpath.sql | 2 + 10 files changed, 515 insertions(+), 120 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 7e1cefde31..e98fee6759 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -179,8 +179,8 @@ jsonpath_in(PG_FUNCTION_ARGS) { char *in = PG_GETARG_CSTRING(0); int32 len = strlen(in); - JsonPathParseItem *jsonpath = parsejsonpath(in, len); - JsonPath *res; + JsonPathParseResult *jsonpath = parsejsonpath(in, len); + JsonPath *res; StringInfoData buf; initStringInfo(&buf); @@ -193,11 +193,13 @@ jsonpath_in(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for jsonpath: \"%s\"", in))); - flattenJsonPathParseItem(&buf, jsonpath, false); + flattenJsonPathParseItem(&buf, jsonpath->expr, false); res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); res->header = JSONPATH_VERSION; + if (jsonpath->lax) + res->header |= JSONPATH_LAX; PG_RETURN_JSONPATH_P(res); } @@ -414,6 +416,9 @@ jsonpath_out(PG_FUNCTION_ARGS) initStringInfo(&buf); enlargeStringInfo(&buf, VARSIZE(in) /* estimation */); + if (!(in->header & JSONPATH_LAX)) + appendBinaryStringInfo(&buf, "strict ", 7); + jspInit(&v, in); printJsonPathItem(&buf, &v, false, true); @@ -447,7 +452,7 @@ jsonpath_out(PG_FUNCTION_ARGS) void jspInit(JsonPathItem *v, JsonPath *js) { - Assert(js->header == JSONPATH_VERSION); + Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION); jspInitByBuffer(v, js->data, 0); } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a0dca7f077..0588326921 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -31,6 +31,9 @@ static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found); +static JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, List **found); + static inline JsonbValue * JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) { @@ -333,6 +336,64 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) return res ? jperOk : jperNotFound; } +static JsonbValue * +copyJsonbValue(JsonbValue *src) +{ + JsonbValue *dst = palloc(sizeof(*dst)); + + *dst = *src; + + return dst; +} + +static inline JsonPathExecResult +recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, List **found) +{ + if (cxt->lax) + { + List *seq = NIL; + JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq); + ListCell *lc; + + if (jperIsError(res)) + return res; + + foreach(lc, seq) + { + JsonbValue *item = lfirst(lc); + + if (item->type == jbvArray) + { + JsonbValue *elem = item->val.array.elems; + JsonbValue *last = elem + item->val.array.nElems; + + for (; elem < last; elem++) + *found = lappend(*found, copyJsonbValue(elem)); + } + else if (item->type == jbvBinary && + JsonContainerIsArray(item->val.binary.data)) + { + JsonbValue elem; + JsonbIterator *it = JsonbIteratorInit(item->val.binary.data); + JsonbIteratorToken tok; + + while ((tok = JsonbIteratorNext(&it, &elem, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + *found = lappend(*found, copyJsonbValue(&elem)); + } + } + else + *found = lappend(*found, item); + } + + return jperOk; + } + + return recursiveExecute(cxt, jsp, jb, found); +} + static JsonPathExecResult executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { @@ -346,14 +407,14 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) bool found = false; jspGetLeftArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, &lseq); - if (res != jperOk) - return res; + res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); + if (jperIsError(res)) + return jperError; jspGetRightArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, &rseq); - if (res != jperOk) - return res; + res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); + if (jperIsError(res)) + return jperError; foreach(llc, lseq) { @@ -401,7 +462,7 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) if (found) /* possible only in strict mode */ return jperOk; - if (error) /* possible only in non-strict mode */ + if (error) /* possible only in lax mode */ return jperError; return jperNotFound; @@ -425,16 +486,17 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetLeftArg(jsp, &elem); - jper = recursiveExecute(cxt, &elem, jb, &lseq); + /* XXX by standard unwrapped only operands of multiplicative expressions */ + jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); if (jper == jperOk) { jspGetRightArg(jsp, &elem); - jper = recursiveExecute(cxt, &elem, jb, &rseq); + jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */ } if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); lval = linitial(lseq); @@ -442,7 +504,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf); if (lval->type != jbvNumeric) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); rval = linitial(rseq); @@ -450,7 +512,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); if (rval->type != jbvNumeric) - return jperError; /* ERRCODE_SINGLETON_JSON_ITEM_REQUIRED; */ + return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); if (!found) return jperOk; @@ -488,16 +550,6 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperOk; } -static JsonbValue * -copyJsonbValue(JsonbValue *src) -{ - JsonbValue *dst = palloc(sizeof(*dst)); - - *dst = *src; - - return dst; -} - static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found) @@ -508,10 +560,10 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, ListCell *lc; jspGetArg(jsp, &elem); - jper = recursiveExecute(cxt, &elem, jb, &seq); + jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); - if (jper == jperError) - return jperError; /* ERRCODE_JSON_NUMBER_NOT_FOUND; */ + if (jperIsError(jper)) + return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND); jper = jperNotFound; @@ -534,7 +586,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, continue; /* skip non-numerics processing */ if (val->type != jbvNumeric) - return jperError; /* ERRCODE_JSON_NUMBER_NOT_FOUND; */ + return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND); val = copyJsonbValue(val); @@ -632,8 +684,8 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, * results */ static JsonPathExecResult -recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found) +recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, List **found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -675,7 +727,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, break; case jpiNot: jspGetArg(jsp, &elem); - switch((res = recursiveExecute(cxt, &elem, jb, NULL))) + switch ((res = recursiveExecute(cxt, &elem, jb, NULL))) { case jperOk: res = jperNotFound; @@ -690,7 +742,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiIsUnknown: jspGetArg(jsp, &elem); res = recursiveExecute(cxt, &elem, jb, NULL); - res = (res == jperError) ? jperOk : jperNotFound; + res = jperIsError(res) ? jperOk : jperNotFound; break; case jpiKey: if (JsonbType(jb) == jbvObject) @@ -718,6 +770,16 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, pfree(v); } } + else if (!cxt->lax) + { + Assert(found); + res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); + } + } + else if (!cxt->lax) + { + Assert(found); + res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); } break; case jpiCurrent: @@ -769,7 +831,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, { res = recursiveExecute(cxt, &elem, &v, found); - if (res == jperError) + if (jperIsError(res)) break; if (res == jperOk && found == NULL) @@ -787,6 +849,8 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } } + else + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; case jpiIndexArray: @@ -810,7 +874,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, { res = recursiveExecute(cxt, &elem, v, found); - if (res == jperError || found == NULL) + if (jperIsError(res) || found == NULL) break; if (res == jperOk && found == NULL) @@ -827,6 +891,8 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } } + else + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; case jpiAnyKey: if (JsonbType(jb) == jbvObject) @@ -846,7 +912,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, { res = recursiveExecute(cxt, &elem, &v, found); - if (res == jperError) + if (jperIsError(res)) break; if (res == jperOk && found == NULL) @@ -864,6 +930,11 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } } + else if (!cxt->lax) + { + Assert(found); + res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND); + } break; case jpiEqual: case jpiNotEqual: @@ -938,7 +1009,22 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } case jpiExists: jspGetArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, NULL); + + if (cxt->lax) + res = recursiveExecute(cxt, &elem, jb, NULL); + else + { + List *vals = NIL; + + /* + * In strict mode we must get a complete list of values + * to check that there are no errors at all. + */ + res = recursiveExecute(cxt, &elem, jb, &vals); + + if (!jperIsError(res)) + res = list_length(vals) > 0 ? jperOk : jperNotFound; + } break; case jpiNull: case jpiBool: @@ -960,6 +1046,109 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return res; } +static JsonPathExecResult +recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, List **found) +{ + if (cxt->lax && JsonbType(jb) == jbvArray) + { + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken tok; + JsonPathExecResult res = jperNotFound; + + it = JsonbIteratorInit(jb->val.binary.data); + + while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + { + res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); + if (jperIsError(res)) + break; + if (res == jperOk && !found) + break; + } + } + + return res; + } + + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); +} + +/* + * Wrap a non-array SQL/JSON item into an array for applying array subscription + * path steps in lax mode. + */ +static JsonbValue * +wrapItem(JsonbValue *jbv) +{ + JsonbParseState *ps = NULL; + JsonbValue jbvbuf; + + switch (JsonbType(jbv)) + { + case jbvArray: + /* Simply return an array item. */ + return jbv; + + case jbvScalar: + /* Extract scalar value from singleton pseudo-array. */ + jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvbuf); + break; + + case jbvObject: + /* + * Need to wrap object into a binary JsonbValue for its unpacking + * in pushJsonbValue(). + */ + if (jbv->type != jbvBinary) + jbv = JsonbWrapInBinary(jbv, &jbvbuf); + break; + + default: + /* Ordinary scalars can be pushed directly. */ + break; + } + + pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); + pushJsonbValue(&ps, WJB_ELEM, jbv); + jbv = pushJsonbValue(&ps, WJB_END_ARRAY, NULL); + + return JsonbWrapInBinary(jbv, NULL); +} + +static JsonPathExecResult +recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + List **found) +{ + check_stack_depth(); + + if (cxt->lax) + { + switch (jsp->type) + { + case jpiKey: + case jpiAnyKey: + /* case jpiAny: */ + case jpiFilter: + /* case jpiMethod: excluding type() and size() */ + return recursiveExecuteUnwrap(cxt, jsp, jb, found); + + case jpiAnyArray: + case jpiIndexArray: + jb = wrapItem(jb); + break; + + default: + break; + } + } + + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); +} + /* * Public interface to jsonpath executor */ @@ -970,13 +1159,28 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) JsonPathItem jsp; JsonbValue jbv; - cxt.vars = vars; - cxt.lax = false; /* FIXME */ - JsonbInitBinary(&jbv, json); - + jspInit(&jsp, path); + cxt.vars = vars; + cxt.lax = (path->header & JSONPATH_LAX) != 0; + + if (!cxt.lax && !foundJson) + { + /* + * In strict mode we must get a complete list of values to check + * that there are no errors at all. + */ + List *vals = NIL; + JsonPathExecResult res = recursiveExecute(&cxt, &jsp, &jbv, &vals); + + if (jperIsError(res)) + return res; + + return list_length(vals) > 0 ? jperOk : jperNotFound; + } + return recursiveExecute(&cxt, &jsp, &jbv, foundJson); } @@ -1060,6 +1264,67 @@ makePassingVars(Jsonb *jb) return vars; } +static void +throwJsonPathError(JsonPathExecResult res) +{ + if (!jperIsError(res)) + return; + + switch (jperGetError(res)) + { + case ERRCODE_JSON_ARRAY_NOT_FOUND: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("SQL/JSON array not found"))); + break; + case ERRCODE_JSON_OBJECT_NOT_FOUND: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("SQL/JSON object not found"))); + break; + case ERRCODE_JSON_MEMBER_NOT_FOUND: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("SQL/JSON member not found"))); + break; + case ERRCODE_JSON_NUMBER_NOT_FOUND: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("SQL/JSON number not found"))); + break; + case ERRCODE_JSON_SCALAR_REQUIRED: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("SQL/JSON scalar required"))); + break; + case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("Singleton SQL/JSON item required"))); + break; + case ERRCODE_NON_NUMERIC_JSON_ITEM: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("Non-numeric SQL/JSON item"))); + break; + case ERRCODE_INVALID_JSON_SUBSCRIPT: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("Invalid SQL/JSON subscript"))); + break; + case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("Invalid argument for SQL/JSON datetime function"))); + break; + default: + ereport(ERROR, + (errcode(jperGetError(res)), + errmsg("Unknown SQL/JSON error"))); + break; + } +} + static Datum jsonb_jsonpath_exists(PG_FUNCTION_ARGS) { @@ -1076,8 +1341,8 @@ jsonb_jsonpath_exists(PG_FUNCTION_ARGS) PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jp, 1); - if (res == jperError) - elog(ERROR, "Something wrong"); + if (jperIsError(res)) + PG_RETURN_NULL(); PG_RETURN_BOOL(res == jperOk); } @@ -1095,7 +1360,7 @@ jsonb_jsonpath_exists3(PG_FUNCTION_ARGS) } static Datum -jsonb_jsonpath_query(PG_FUNCTION_ARGS) +jsonb_jsonpath_query(FunctionCallInfo fcinfo) { FuncCallContext *funcctx; List *found = NIL; @@ -1119,8 +1384,8 @@ jsonb_jsonpath_query(PG_FUNCTION_ARGS) res = executeJsonPath(jp, vars, jb, &found); - if (res == jperError) - elog(ERROR, "Something wrong"); + if (jperIsError(res)) + throwJsonPathError(res); PG_FREE_IF_COPY(jp, 1); diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 06d8cb7105..e69eecf6e5 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -198,23 +198,27 @@ makeAny(int first, int last) %expect 0 %name-prefix="jsonpath_yy" %error-verbose -%parse-param {JsonPathParseItem **result} +%parse-param {JsonPathParseResult **result} %union { string str; List *elems; /* list of JsonPathParseItem */ List *indexs; /* list of integers */ JsonPathParseItem *value; + JsonPathParseResult *result; int optype; + bool boolean; } %token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P -%token ANY_P +%token ANY_P STRICT_P LAX_P -%type result scalar_value path_primary expr pexpr array_accessor +%type result + +%type scalar_value path_primary expr pexpr array_accessor any_path accessor_op key predicate delimited_predicate %type accessor_expr @@ -223,6 +227,8 @@ makeAny(int first, int last) %type comp_op +%type mode + %left OR_P %left AND_P %right NOT_P @@ -235,10 +241,20 @@ makeAny(int first, int last) %% result: - expr { *result = $1; } + mode expr { + *result = palloc(sizeof(JsonPathParseResult)); + (*result)->expr = $2; + (*result)->lax = $1; + } | /* EMPTY */ { *result = NULL; } ; +mode: + STRICT_P { $$ = false; } + | LAX_P { $$ = true; } + | /* EMPTY */ { $$ = true; } + ; + scalar_value: STRING_P { $$ = makeItemString(&$1); } | NULL_P { $$ = makeItemString(NULL); } @@ -359,6 +375,8 @@ key: | IS_P { $$ = makeItemKey(&$1); } | UNKNOWN_P { $$ = makeItemKey(&$1); } | EXISTS_P { $$ = makeItemKey(&$1); } + | STRICT_P { $$ = makeItemKey(&$1); } + | LAX_P { $$ = makeItemKey(&$1); } ; %% diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index bed0d4fec4..25fc54c782 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -235,7 +235,7 @@ unicode \\u[0-9A-Fa-f]{4} %% void -jsonpath_yyerror(JsonPathParseItem **result, const char *message) +jsonpath_yyerror(JsonPathParseResult **result, const char *message) { if (*yytext == YY_END_OF_BUFFER_CHAR) { @@ -271,10 +271,12 @@ typedef struct keyword static keyword keywords[] = { { 2, false, IS_P, "is"}, { 2, false, TO_P, "to"}, + { 3, false, LAX_P, "lax"}, { 4, true, NULL_P, "null"}, { 4, true, TRUE_P, "true"}, { 5, true, FALSE_P, "false"}, { 6, false, EXISTS_P, "exists"}, + { 6, false, STRICT_P, "strict"}, { 7, false, UNKNOWN_P, "unknown"} }; @@ -394,9 +396,9 @@ addchar(bool init, char s) { scanstring.len++; } -JsonPathParseItem* +JsonPathParseResult * parsejsonpath(const char *str, int len) { - JsonPathParseItem *parseresult; + JsonPathParseResult *parseresult; jsonpath_scanner_init(str, len); diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 18042ada5d..baa2791c55 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -26,6 +26,7 @@ typedef struct } JsonPath; #define JSONPATH_VERSION (0x01) +#define JSONPATH_LAX (0x80000000) #define JSONPATH_HDRSZ (offsetof(JsonPath, data)) #define DatumGetJsonPathP(d) ((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM(d))) @@ -174,18 +175,33 @@ struct JsonPathParseItem { } value; }; -extern JsonPathParseItem* parsejsonpath(const char *str, int len); +typedef struct JsonPathParseResult +{ + JsonPathParseItem *expr; + bool lax; +} JsonPathParseResult; + +extern JsonPathParseResult* parsejsonpath(const char *str, int len); /* * Evaluation of jsonpath */ -typedef enum JsonPathExecResult { +typedef enum JsonPathExecStatus +{ jperOk = 0, jperError, jperFatalError, jperNotFound -} JsonPathExecResult; +} JsonPathExecStatus; + +typedef uint64 JsonPathExecResult; + +#define jperStatus(jper) ((JsonPathExecStatus)(uint32)(jper)) +#define jperIsError(jper) (jperStatus(jper) == jperError) +#define jperGetError(jper) ((uint32)((jper) >> 32)) +#define jperMakeError(err) (((uint64)(err) << 32) | jperError) +#define jperFree(jper) ((void) 0) typedef Datum (*JsonPathVariable_cb)(void *, bool *); diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h index c7be6bea88..1c8447f6bf 100644 --- a/src/include/utils/jsonpath_scanner.h +++ b/src/include/utils/jsonpath_scanner.h @@ -25,6 +25,6 @@ typedef struct string { /* flex 2.5.4 doesn't bother with a decl for this */ extern int jsonpath_yylex(YYSTYPE * yylval_param); -extern void jsonpath_yyerror(JsonPathParseItem **result, const char *message); +extern void jsonpath_yyerror(JsonPathParseResult **result, const char *message); #endif diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index abc04eba99..ff35e6316d 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -40,19 +40,19 @@ select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); _jsonpath_exists ------------------ t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{2}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); _jsonpath_exists ------------------ t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{3}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{3}'); _jsonpath_exists ------------------ f @@ -94,7 +94,13 @@ select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @. t (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'lax $ ? (@.a[*] >= @.b[*])'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'strict $ ? (@.a[*] >= @.b[*])'); _jsonpath_exists ------------------ f @@ -137,53 +143,85 @@ select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); {"a": 13} (2 rows) -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*.a'); +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); _jsonpath_query ----------------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].a'); _jsonpath_query ----------------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].*'); _jsonpath_query ----------------- 13 14 (2 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0].a'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[1].a'); _jsonpath_query ----------------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[2].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[2].a'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0,1].a'); _jsonpath_query ----------------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 10].a'); _jsonpath_query ----------------- 13 (1 row) +select * from _jsonpath_query(jsonb '1', 'lax $[0]'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '1', 'lax $[*]'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); + _jsonpath_query +----------------- + 1 + 2 + 3 +(3 rows) + select * from _jsonpath_query(jsonb '{"a": 10}', '$'); _jsonpath_query ----------------- @@ -257,7 +295,7 @@ select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); null (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**'); _jsonpath_query ----------------- {"a": {"b": 1}} @@ -265,106 +303,106 @@ select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); 1 (3 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); _jsonpath_query ----------------- {"b": 1} (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}'); _jsonpath_query ----------------- {"b": 1} 1 (2 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2,}'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{3,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3,}'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0,}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,2}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); _jsonpath_query ----------------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0,}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,2}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? (@ > 0)'); _jsonpath_query ----------------- 1 @@ -642,3 +680,27 @@ select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); f (1 row) +-- unwrapping of operator arguments in lax mode +select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); + _jsonpath_query +----------------- + 6 +(1 row) + +select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3'); + _jsonpath_query +----------------- + 5 +(1 row) + +select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); + _jsonpath_query +----------------- + -2 + -3 + -4 +(3 rows) + +-- should fail +select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); +ERROR: Singleton SQL/JSON item required diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 2599fcbbae..10b1a7efae 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -9,6 +9,18 @@ select '$'::jsonpath; $ (1 row) +select 'strict $'::jsonpath; + jsonpath +---------- + strict $ +(1 row) + +select 'lax $'::jsonpath; + jsonpath +---------- + $ +(1 row) + select '$.a'::jsonpath; jsonpath ---------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 4981e0ddd2..e484f6ce3b 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -5,9 +5,9 @@ select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); select _jsonpath_exists(jsonb '{}', '$.*'); select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{2}'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{3}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); +select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{3}'); select _jsonpath_exists(jsonb '[]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[*]'); @@ -15,7 +15,8 @@ select _jsonpath_exists(jsonb '[1]', '$.[1]'); select _jsonpath_exists(jsonb '[1]', '$.[0]'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'lax $ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'strict $ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); @@ -23,14 +24,19 @@ select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*.a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[*].*'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[1].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[2].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0,1].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', '$.[0 to 10].a'); +select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].*'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[2].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0,1].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 10].a'); +select * from _jsonpath_query(jsonb '1', 'lax $[0]'); +select * from _jsonpath_query(jsonb '1', 'lax $[*]'); +select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); +select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); +select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); select * from _jsonpath_query(jsonb '{"a": 10}', '$'); select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); @@ -45,25 +51,25 @@ select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value) select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{2,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{3,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3,}'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? (@ > 0)'); select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); @@ -132,3 +138,10 @@ select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); + +-- unwrapping of operator arguments in lax mode +select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); +select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3'); +select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); +-- should fail +select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 6fcdffc7c5..0f70bfe5d1 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -2,6 +2,8 @@ select ''::jsonpath; select '$'::jsonpath; +select 'strict $'::jsonpath; +select 'lax $'::jsonpath; select '$.a'::jsonpath; select '$.a.v'::jsonpath; select '$.a.*'::jsonpath; From d5aebddff94c892934be729cdb3eb03e58006a4d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 6 Mar 2017 23:36:13 +0300 Subject: [PATCH 30/97] Add jsonpath item methods --- src/backend/utils/adt/jsonpath.c | 51 +++- src/backend/utils/adt/jsonpath_exec.c | 302 ++++++++++++++++++- src/backend/utils/adt/jsonpath_gram.y | 53 +++- src/backend/utils/adt/jsonpath_scan.l | 10 +- src/include/utils/jsonpath.h | 10 +- src/test/regress/expected/jsonb_jsonpath.out | 149 +++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 29 ++ 7 files changed, 588 insertions(+), 16 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index e98fee6759..7bffbff3e9 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -163,6 +163,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, (char*)&item->value.anybounds.last, sizeof(item->value.anybounds.last)); break; + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiDatetime: + case jpiKeyValue: + break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -398,6 +407,30 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket appendStringInfo(buf, "**{%u,%u}", v->content.anybounds.first, v->content.anybounds.last); break; + case jpiType: + appendBinaryStringInfo(buf, ".type()", 7); + break; + case jpiSize: + appendBinaryStringInfo(buf, ".size()", 7); + break; + case jpiAbs: + appendBinaryStringInfo(buf, ".abs()", 6); + break; + case jpiFloor: + appendBinaryStringInfo(buf, ".floor()", 8); + break; + case jpiCeiling: + appendBinaryStringInfo(buf, ".ceiling()", 10); + break; + case jpiDouble: + appendBinaryStringInfo(buf, ".double()", 9); + break; + case jpiDatetime: + appendBinaryStringInfo(buf, ".datetime()", 11); + break; + case jpiKeyValue: + appendBinaryStringInfo(buf, ".keyvalue()", 11); + break; default: elog(ERROR, "Unknown jsonpath item type: %d", v->type); } @@ -483,6 +516,14 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiCurrent: case jpiAnyArray: case jpiAnyKey: + case jpiType: + case jpiSize: + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiDatetime: + case jpiKeyValue: break; case jpiKey: case jpiString: @@ -559,7 +600,15 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiFilter || v->type == jpiCurrent || v->type == jpiExists || - v->type == jpiRoot + v->type == jpiRoot || + v->type == jpiType || + v->type == jpiSize || + v->type == jpiAbs || + v->type == jpiFloor || + v->type == jpiCeiling || + v->type == jpiDouble || + v->type == jpiDatetime || + v->type == jpiKeyValue ); if (a) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0588326921..ded1c3c74f 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -221,6 +221,69 @@ JsonbType(JsonbValue *jb) return type; } +static const char * +JsonbTypeName(JsonbValue *jb) +{ + JsonbValue jbvbuf; + + if (jb->type == jbvBinary) + { + JsonbContainer *jbc = jb->val.binary.data; + + if (JsonContainerIsScalar(jbc)) + jb = JsonbExtractScalar(jbc, &jbvbuf); + else if (JsonContainerIsArray(jbc)) + return "array"; + else if (JsonContainerIsObject(jbc)) + return "object"; + else + elog(ERROR, "Unknown container type: 0x%08x", jbc->header); + } + + switch (jb->type) + { + case jbvObject: + return "object"; + case jbvArray: + return "array"; + case jbvNumeric: + return "number"; + case jbvString: + return "string"; + case jbvBool: + return "boolean"; + case jbvNull: + return "null"; + /* TODO + return "date"; + return "time without time zone"; + return "time with time zone"; + return "timestamp without time zone"; + return "timestamp with time zone"; + */ + default: + elog(ERROR, "Unknown jsonb value type: %d", jb->type); + return "unknown"; + } +} + +static int +JsonbArraySize(JsonbValue *jb) +{ + if (jb->type == jbvArray) + return jb->val.array.nElems; + + if (jb->type == jbvBinary) + { + JsonbContainer *jbc = jb->val.binary.data; + + if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) + return JsonContainerSize(jbc); + } + + return -1; +} + static int compareNumeric(Numeric a, Numeric b) { @@ -1039,6 +1102,237 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, *found = lappend(*found, jbv); } break; + case jpiType: + { + JsonbValue *jbv = palloc(sizeof(*jbv)); + + jbv->type = jbvString; + jbv->val.string.val = pstrdup(JsonbTypeName(jb)); + jbv->val.string.len = strlen(jbv->val.string.val); + + res = jperOk; + + if (jspGetNext(jsp, &elem)) + res = recursiveExecute(cxt, &elem, jbv, found); + else if (found) + *found = lappend(*found, jbv); + } + break; + case jpiSize: + { + int size = JsonbArraySize(jb); + + if (size < 0) + { + if (!cxt->lax) + { + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); + break; + } + + size = 1; + } + + jb = palloc(sizeof(*jb)); + + jb->type = jbvNumeric; + jb->val.numeric = + DatumGetNumeric(DirectFunctionCall1(int4_numeric, + Int32GetDatum(size))); + + res = jperOk; + + if (jspGetNext(jsp, &elem)) + res = recursiveExecute(cxt, &elem, jb, found); + else if (found) + *found = lappend(*found, jb); + } + break; + case jpiAbs: + case jpiFloor: + case jpiCeiling: + { + JsonbValue jbvbuf; + + if (JsonbType(jb) == jbvScalar) + jb = JsonbExtractScalar(jb->val.binary.data, &jbvbuf); + + if (jb->type == jbvNumeric) + { + Datum datum = NumericGetDatum(jb->val.numeric); + + switch (jsp->type) + { + case jpiAbs: + datum = DirectFunctionCall1(numeric_abs, datum); + break; + case jpiFloor: + datum = DirectFunctionCall1(numeric_floor, datum); + break; + case jpiCeiling: + datum = DirectFunctionCall1(numeric_ceil, datum); + break; + default: + break; + } + + jb = palloc(sizeof(*jb)); + + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(datum); + + res = jperOk; + + if (jspGetNext(jsp, &elem)) + res = recursiveExecute(cxt, &elem, jb, found); + else if (found) + *found = lappend(*found, jb); + } + else + res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); + } + break; + case jpiDouble: + { + JsonbValue jbv; + MemoryContext mcxt = CurrentMemoryContext; + + if (JsonbType(jb) == jbvScalar) + jb = JsonbExtractScalar(jb->val.binary.data, &jbv); + + PG_TRY(); + { + if (jb->type == jbvNumeric) + { + /* only check success of numeric to double cast */ + DirectFunctionCall1(numeric_float8, + NumericGetDatum(jb->val.numeric)); + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as double */ + char *str = pnstrdup(jb->val.string.val, + jb->val.string.len); + Datum val = DirectFunctionCall1( + float8in, CStringGetDatum(str)); + pfree(str); + + jb = &jbv; + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(DirectFunctionCall1( + float8_numeric, val)); + res = jperOk; + + } + else + res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) != + ERRCODE_DATA_EXCEPTION) + PG_RE_THROW(); + + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); + } + PG_END_TRY(); + + if (res == jperOk) + { + if (jspGetNext(jsp, &elem)) + res = recursiveExecute(cxt, &elem, jb, found); + else if (found) + *found = lappend(*found, copyJsonbValue(jb)); + } + } + break; + case jpiDatetime: + /* TODO */ + break; + case jpiKeyValue: + if (JsonbType(jb) != jbvObject) + res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND); + else + { + int32 r; + JsonbValue key; + JsonbValue val; + JsonbValue obj; + JsonbValue keystr; + JsonbValue valstr; + JsonbIterator *it; + JsonbParseState *ps = NULL; + + hasNext = jspGetNext(jsp, &elem); + + if (!JsonContainerSize(jb->val.binary.data)) + { + res = jperNotFound; + break; + } + + /* make template object */ + obj.type = jbvBinary; + + keystr.type = jbvString; + keystr.val.string.val = "key"; + keystr.val.string.len = 3; + + valstr.type = jbvString; + valstr.val.string.val = "value"; + valstr.val.string.len = 5; + + it = JsonbIteratorInit(jb->val.binary.data); + + while ((r = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) + { + if (r == WJB_KEY) + { + Jsonb *jsonb; + JsonbValue *keyval; + + res = jperOk; + + if (!hasNext && !found) + break; + + r = JsonbIteratorNext(&it, &val, true); + Assert(r == WJB_VALUE); + + pushJsonbValue(&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); + + keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); + + jsonb = JsonbValueToJsonb(keyval); + + JsonbInitBinary(&obj, jsonb); + + if (hasNext) + { + res = recursiveExecute(cxt, &elem, &obj, found); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + else + *found = lappend(*found, copyJsonbValue(&obj)); + } + } + } + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } @@ -1133,7 +1427,13 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiAnyKey: /* case jpiAny: */ case jpiFilter: - /* case jpiMethod: excluding type() and size() */ + /* all methods excluding type() and size() */ + case jpiAbs: + case jpiFloor: + case jpiCeiling: + case jpiDouble: + case jpiDatetime: + case jpiKeyValue: return recursiveExecuteUnwrap(cxt, jsp, jb, found); case jpiAnyArray: diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index e69eecf6e5..a1bc2420ae 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -206,7 +206,7 @@ makeAny(int first, int last) List *indexs; /* list of integers */ JsonPathParseItem *value; JsonPathParseResult *result; - int optype; + JsonPathItemType optype; bool boolean; } @@ -215,6 +215,8 @@ makeAny(int first, int last) %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P %token ANY_P STRICT_P LAX_P +%token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P +%token KEYVALUE_P %type result @@ -225,10 +227,13 @@ makeAny(int first, int last) %type index_elem index_list -%type comp_op +%type comp_op method %type mode +%type key_name + + %left OR_P %left AND_P %right NOT_P @@ -363,20 +368,44 @@ accessor_op: | array_accessor { $$ = $1; } | '.' array_accessor { $$ = $2; } | '.' any_path { $$ = $2; } + | '.' method '(' ')' { $$ = makeItemType($2); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } ; key: - STRING_P { $$ = makeItemKey(&$1); } - | TO_P { $$ = makeItemKey(&$1); } - | NULL_P { $$ = makeItemKey(&$1); } - | TRUE_P { $$ = makeItemKey(&$1); } - | FALSE_P { $$ = makeItemKey(&$1); } - | IS_P { $$ = makeItemKey(&$1); } - | UNKNOWN_P { $$ = makeItemKey(&$1); } - | EXISTS_P { $$ = makeItemKey(&$1); } - | STRICT_P { $$ = makeItemKey(&$1); } - | LAX_P { $$ = makeItemKey(&$1); } + key_name { $$ = makeItemKey(&$1); } + ; + +key_name: + STRING_P + | TO_P + | NULL_P + | TRUE_P + | FALSE_P + | IS_P + | UNKNOWN_P + | EXISTS_P + | STRICT_P + | LAX_P + | ABS_P + | SIZE_P + | TYPE_P + | FLOOR_P + | DOUBLE_P + | CEILING_P + | DATETIME_P + | KEYVALUE_P + ; + +method: + ABS_P { $$ = jpiAbs; } + | SIZE_P { $$ = jpiSize; } + | TYPE_P { $$ = jpiType; } + | FLOOR_P { $$ = jpiFloor; } + | DOUBLE_P { $$ = jpiDouble; } + | CEILING_P { $$ = jpiCeiling; } + | DATETIME_P { $$ = jpiDatetime; } + | KEYVALUE_P { $$ = jpiKeyValue; } ; %% diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 25fc54c782..0929c8eac7 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -271,13 +271,21 @@ typedef struct keyword static keyword keywords[] = { { 2, false, IS_P, "is"}, { 2, false, TO_P, "to"}, + { 3, false, ABS_P, "abs"}, { 3, false, LAX_P, "lax"}, { 4, true, NULL_P, "null"}, + { 4, false, SIZE_P, "size"}, { 4, true, TRUE_P, "true"}, + { 4, false, TYPE_P, "type"}, { 5, true, FALSE_P, "false"}, + { 5, false, FLOOR_P, "floor"}, + { 6, false, DOUBLE_P, "double"}, { 6, false, EXISTS_P, "exists"}, { 6, false, STRICT_P, "strict"}, - { 7, false, UNKNOWN_P, "unknown"} + { 7, false, CEILING_P, "ceiling"}, + { 7, false, UNKNOWN_P, "unknown"}, + { 8, false, DATETIME_P, "datetime"}, + { 8, false, KEYVALUE_P, "keyvalue"}, }; static int diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index baa2791c55..c11a3ab53a 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -69,7 +69,15 @@ typedef enum JsonPathItemType { jpiRoot, jpiVariable, jpiFilter, - jpiExists + jpiExists, + jpiType, + jpiSize, + jpiAbs, + jpiFloor, + jpiCeiling, + jpiDouble, + jpiDatetime, + jpiKeyValue, } JsonPathItemType; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index ff35e6316d..8981e0befa 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -704,3 +704,152 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); -- should fail select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); ERROR: Singleton SQL/JSON item required +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); + _jsonpath_query +----------------- + "array" +(1 row) + +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); + _jsonpath_query +----------------- + "array" +(1 row) + +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); + _jsonpath_query +----------------- + "null" + "number" + "boolean" + "string" + "array" + "object" +(6 rows) + +select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); +ERROR: SQL/JSON array not found +select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); + _jsonpath_query +----------------- + 1 + 1 + 1 + 1 + 0 + 1 + 3 + 1 + 1 +(9 rows) + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); + _jsonpath_query +----------------- + 0 + 1 + 2 + 3.4 + 5.6 +(5 rows) + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); + _jsonpath_query +----------------- + 0 + 1 + -2 + -4 + 5 +(5 rows) + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); + _jsonpath_query +----------------- + 0 + 1 + -2 + -3 + 6 +(5 rows) + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); + _jsonpath_query +----------------- + 0 + 1 + 2 + 3 + 6 +(5 rows) + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); + _jsonpath_query +----------------- + "number" + "number" + "number" + "number" + "number" +(5 rows) + +select _jsonpath_query(jsonb '[{},1]', '$[*].keyvalue()'); +ERROR: SQL/JSON object not found +select _jsonpath_query(jsonb '{}', '$.keyvalue()'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); + _jsonpath_query +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); + _jsonpath_query +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); +ERROR: SQL/JSON object not found +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); + _jsonpath_query +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select _jsonpath_query(jsonb 'null', '$.double()'); +ERROR: Non-numeric SQL/JSON item +select _jsonpath_query(jsonb 'true', '$.double()'); +ERROR: Non-numeric SQL/JSON item +select _jsonpath_query(jsonb '[]', '$.double()'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '[]', 'strict $.double()'); +ERROR: Non-numeric SQL/JSON item +select _jsonpath_query(jsonb '{}', '$.double()'); +ERROR: Non-numeric SQL/JSON item +select _jsonpath_query(jsonb '1.23', '$.double()'); + _jsonpath_query +----------------- + 1.23 +(1 row) + +select _jsonpath_query(jsonb '"1.23"', '$.double()'); + _jsonpath_query +----------------- + 1.23 +(1 row) + +select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); +ERROR: Non-numeric SQL/JSON item diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index e484f6ce3b..8922000a5d 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -145,3 +145,32 @@ select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3'); select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); -- should fail select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); + +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); +select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); + +select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); +select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); + +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); +select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); + +select _jsonpath_query(jsonb '[{},1]', '$[*].keyvalue()'); +select _jsonpath_query(jsonb '{}', '$.keyvalue()'); +select _jsonpath_query(jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); +select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); + +select _jsonpath_query(jsonb 'null', '$.double()'); +select _jsonpath_query(jsonb 'true', '$.double()'); +select _jsonpath_query(jsonb '[]', '$.double()'); +select _jsonpath_query(jsonb '[]', 'strict $.double()'); +select _jsonpath_query(jsonb '{}', '$.double()'); +select _jsonpath_query(jsonb '1.23', '$.double()'); +select _jsonpath_query(jsonb '"1.23"', '$.double()'); +select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); From 7b3c96b9c92e2e39598600dde5eb162fb0d2d693 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 6 Mar 2017 23:37:10 +0300 Subject: [PATCH 31/97] Use JsonContainerXxx() macros instead of raw jsonb flags --- src/backend/utils/adt/jsonpath_exec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index ded1c3c74f..50b63d952b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -208,11 +208,11 @@ JsonbType(JsonbValue *jb) { JsonbContainer *jbc = jb->val.binary.data; - if (jbc->header & JB_FSCALAR) + if (JsonContainerIsScalar(jbc)) type = jbvScalar; - else if (jbc->header & JB_FOBJECT) + else if (JsonContainerIsObject(jbc)) type = jbvObject; - else if (jbc->header & JB_FARRAY) + else if (JsonContainerIsArray(jbc)) type = jbvArray; else elog(ERROR, "Unknown container type: 0x%08x", jbc->header); From cd5333667e47a763c05caef3d5a037529af4e63d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:50:12 +0300 Subject: [PATCH 32/97] Allow jsonpath scalars to be a part of path --- src/backend/utils/adt/jsonpath.c | 22 ++------- src/backend/utils/adt/jsonpath_exec.c | 19 ++++++-- src/test/regress/expected/jsonb_jsonpath.out | 30 ++++++++++++ src/test/regress/expected/jsonpath.out | 48 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 5 ++ src/test/regress/sql/jsonpath.sql | 9 ++++ 6 files changed, 111 insertions(+), 22 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 7bffbff3e9..2818396164 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -48,11 +48,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, { case jpiString: case jpiVariable: - /* scalars aren't checked during grammar parse */ - if (item->next != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("scalar could not be a part of path"))); case jpiKey: appendBinaryStringInfo(buf, (char*)&item->value.string.len, sizeof(item->value.string.len)); @@ -60,18 +55,10 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, appendStringInfoChar(buf, '\0'); break; case jpiNumeric: - if (item->next != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("scalar could not be a part of path"))); appendBinaryStringInfo(buf, (char*)item->value.numeric, VARSIZE(item->value.numeric)); break; case jpiBool: - if (item->next != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("scalar could not be a part of path"))); appendBinaryStringInfo(buf, (char*)&item->value.boolean, sizeof(item->value.boolean)); break; @@ -126,10 +113,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, } break; case jpiNull: - if (item->next != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("scalar could not be a part of path"))); break; case jpiRoot: if (forbiddenRoot) @@ -592,6 +575,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) if (v->nextPos > 0) { Assert( + v->type == jpiString || + v->type == jpiNumeric || + v->type == jpiBool || + v->type == jpiNull || v->type == jpiKey || v->type == jpiAny || v->type == jpiAnyArray || @@ -601,6 +588,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiCurrent || v->type == jpiExists || v->type == jpiRoot || + v->type == jpiVariable || v->type == jpiType || v->type == jpiSize || v->type == jpiAbs || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 50b63d952b..4bae8af8e0 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1094,12 +1094,21 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiNumeric: case jpiString: case jpiVariable: - res = jperOk; - if (found) + if (jspGetNext(jsp, &elem)) { - JsonbValue *jbv = palloc(sizeof(*jbv)); - computeJsonPathItem(cxt, jsp, jbv); - *found = lappend(*found, jbv); + JsonbValue jbv; + computeJsonPathItem(cxt, jsp, &jbv); + res = recursiveExecute(cxt, &elem, &jbv, found); + } + else + { + res = jperOk; + if (found) + { + JsonbValue *jbv = palloc(sizeof(*jbv)); + computeJsonPathItem(cxt, jsp, jbv); + *found = lappend(*found, jbv); + } } break; case jpiType: diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 8981e0befa..d39763273e 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -727,6 +727,36 @@ select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); "object" (6 rows) +select _jsonpath_query(jsonb 'null', 'null.type()'); + _jsonpath_query +----------------- + "null" +(1 row) + +select _jsonpath_query(jsonb 'null', 'true.type()'); + _jsonpath_query +----------------- + "boolean" +(1 row) + +select _jsonpath_query(jsonb 'null', '123.type()'); + _jsonpath_query +----------------- + "number" +(1 row) + +select _jsonpath_query(jsonb 'null', '"123".type()'); + _jsonpath_query +----------------- + "string" +(1 row) + +select _jsonpath_query(jsonb 'null', 'aaa.type()'); + _jsonpath_query +----------------- + "string" +(1 row) + select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); ERROR: SQL/JSON array not found select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 10b1a7efae..479d8e7818 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -267,6 +267,24 @@ select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; $."g"?(("x" >= 123 || "a" == 4) && exists (@."x"?(@ == 14))) (1 row) +select '$a'::jsonpath; + jsonpath +---------- + $"a" +(1 row) + +select '$a.b'::jsonpath; + jsonpath +---------- + $"a"."b" +(1 row) + +select '$a[*]'::jsonpath; + jsonpath +---------- + $"a"[*] +(1 row) + select '$.g ? (zip == $zip)'::jsonpath; jsonpath ------------------------- @@ -285,6 +303,36 @@ select '$.a[1,2, 3 to 16]'::jsonpath; $."a"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] (1 row) +select 'null.type()'::jsonpath; + jsonpath +------------- + null.type() +(1 row) + +select '1.type()'::jsonpath; + jsonpath +---------- + 1.type() +(1 row) + +select '"aaa".type()'::jsonpath; + jsonpath +-------------- + "aaa".type() +(1 row) + +select 'aaa.type()'::jsonpath; + jsonpath +-------------- + "aaa".type() +(1 row) + +select 'true.type()'::jsonpath; + jsonpath +------------- + true.type() +(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 8922000a5d..db53e86077 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -149,6 +149,11 @@ select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); +select _jsonpath_query(jsonb 'null', 'null.type()'); +select _jsonpath_query(jsonb 'null', 'true.type()'); +select _jsonpath_query(jsonb 'null', '123.type()'); +select _jsonpath_query(jsonb 'null', '"123".type()'); +select _jsonpath_query(jsonb 'null', 'aaa.type()'); select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 0f70bfe5d1..b8d3f9836b 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -47,10 +47,19 @@ select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath; select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; +select '$a'::jsonpath; +select '$a.b'::jsonpath; +select '$a[*]'::jsonpath; select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; +select 'null.type()'::jsonpath; +select '1.type()'::jsonpath; +select '"aaa".type()'::jsonpath; +select 'aaa.type()'::jsonpath; +select 'true.type()'::jsonpath; + select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; select '$ ? (a < +1)'::jsonpath; From 22bed4304d6aa48a139fa27f6715c1df5cd9edfc Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:51:41 +0300 Subject: [PATCH 33/97] Implement jsonpath unary operations output --- src/backend/utils/adt/jsonpath.c | 17 ++++++++++++++++- src/test/regress/expected/jsonpath.out | 6 ++++++ src/test/regress/sql/jsonpath.sql | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 2818396164..fb26c06fad 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -255,8 +255,11 @@ operationPriority(JsonPathItemType op) case jpiDiv: case jpiMod: return 4; - default: + case jpiPlus: + case jpiMinus: return 5; + default: + return 6; } } @@ -323,6 +326,18 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket if (printBracketes) appendStringInfoChar(buf, ')'); break; + case jpiPlus: + case jpiMinus: + if (printBracketes) + appendStringInfoChar(buf, '('); + appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-'); + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + if (printBracketes) + appendStringInfoChar(buf, ')'); + break; case jpiFilter: appendBinaryStringInfo(buf, "?(", 2); jspGetArg(v, &elem); diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 479d8e7818..c2cd893b60 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -267,6 +267,12 @@ select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; $."g"?(("x" >= 123 || "a" == 4) && exists (@."x"?(@ == 14))) (1 row) +select '$.g ? (+x >= +-(+a + 2))'::jsonpath; + jsonpath +-------------------------------- + $."g"?(+"x" >= +(-(+"a" + 2))) +(1 row) + select '$a'::jsonpath; jsonpath ---------- diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index b8d3f9836b..37e3e31a0a 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -46,6 +46,7 @@ select '$.g ? (exists (.x))'::jsonpath; select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath; select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; +select '$.g ? (+x >= +-(+a + 2))'::jsonpath; select '$a'::jsonpath; select '$a.b'::jsonpath; From 7a091062d3f03f925f59c36f87b0bfe12c00a03a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:54:02 +0300 Subject: [PATCH 34/97] Add jsonpath array index expressions --- src/backend/utils/adt/jsonpath.c | 76 ++++++++++-- src/backend/utils/adt/jsonpath_exec.c | 115 ++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 29 +++-- src/include/utils/jsonpath.h | 14 ++- src/test/regress/expected/jsonb_jsonpath.out | 80 ++++++++++--- src/test/regress/expected/jsonpath.out | 18 ++- src/test/regress/sql/jsonb_jsonpath.sql | 39 ++++--- src/test/regress/sql/jsonpath.sql | 1 + 8 files changed, 291 insertions(+), 81 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index fb26c06fad..82c689414d 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -130,13 +130,39 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, errmsg("@ is not allowed in root expressions"))); break; case jpiIndexArray: - appendBinaryStringInfo(buf, - (char*)&item->value.array.nelems, - sizeof(item->value.array.nelems)); - appendBinaryStringInfo(buf, - (char*)item->value.array.elems, - item->value.array.nelems * - sizeof(item->value.array.elems[0])); + { + int32 nelems = item->value.array.nelems; + int offset; + int i; + + appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems)); + + offset = buf->len; + + appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems); + + for (i = 0; i < nelems; i++) + { + int32 *ppos; + int32 topos; + int32 frompos = + flattenJsonPathParseItem(buf, + item->value.array.elems[i].from, + true); + + if (item->value.array.elems[i].to) + topos = flattenJsonPathParseItem(buf, + item->value.array.elems[i].to, + true); + else + topos = 0; + + ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)]; + + ppos[0] = frompos; + ppos[1] = topos; + } + } break; case jpiAny: appendBinaryStringInfo(buf, @@ -380,11 +406,22 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket break; case jpiIndexArray: appendStringInfoChar(buf, '['); - for(i = 0; i< v->content.array.nelems; i++) + for (i = 0; i < v->content.array.nelems; i++) { + JsonPathItem from; + JsonPathItem to; + bool range = jspGetArraySubscript(v, &from, &to, i); + if (i) appendStringInfoChar(buf, ','); - appendStringInfo(buf, "%d", v->content.array.elems[i]); + + printJsonPathItem(buf, &from, false, false); + + if (range) + { + appendBinaryStringInfo(buf, " to ", 4); + printJsonPathItem(buf, &to, false, false); + } } appendStringInfoChar(buf, ']'); break; @@ -473,7 +510,7 @@ jsonpath_out(PG_FUNCTION_ARGS) } while(0) \ #define read_int32_n(v, b, p, n) do { \ - (v) = (int32*)((b) + (p)); \ + (v) = (void *)((b) + (p)); \ (p) += sizeof(int32) * (n); \ } while(0) \ @@ -558,7 +595,8 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) break; case jpiIndexArray: read_int32(v->content.array.nelems, base, pos); - read_int32_n(v->content.array.elems, base, pos, v->content.array.nelems); + read_int32_n(v->content.array.elems, base, pos, + v->content.array.nelems * 2); break; case jpiAny: read_int32(v->content.anybounds.first, base, pos); @@ -695,3 +733,19 @@ jspGetString(JsonPathItem *v, int32 *len) *len = v->content.value.datalen; return v->content.value.data; } + +bool +jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to, + int i) +{ + Assert(v->type == jpiIndexArray); + + jspInitByBuffer(from, v->base, v->content.array.elems[i].from); + + if (!v->content.array.elems[i].to) + return false; + + jspInitByBuffer(to, v->base, v->content.array.elems[i].to); + + return true; +} diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 4bae8af8e0..a765d25583 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -736,6 +736,37 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return res; } +static JsonPathExecResult +getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, + int32 *index) +{ + JsonbValue *jbv; + List *found = NIL; + JsonbValue tmp; + JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found); + + if (jperIsError(res)) + return res; + + if (list_length(found) != 1) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + + jbv = linitial(found); + + if (JsonbType(jbv) == jbvScalar) + jbv = JsonbExtractScalar(jbv->val.binary.data, &tmp); + + if (jbv->type != jbvNumeric) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + + *index = DatumGetInt32(DirectFunctionCall1(numeric_int4, + DirectFunctionCall2(numeric_trunc, + NumericGetDatum(jbv->val.numeric), + Int32GetDatum(0)))); + + return jperOk; +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -919,39 +950,87 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiIndexArray: if (JsonbType(jb) == jbvArray) { - JsonbValue *v; - int i; + int i; + int size = JsonbArraySize(jb); hasNext = jspGetNext(jsp, &elem); - for(i=0; icontent.array.nelems; i++) + for (i = 0; i < jsp->content.array.nelems; i++) { - /* TODO for future: array index can be expression */ - v = getIthJsonbValueFromContainer(jb->val.binary.data, - jsp->content.array.elems[i]); + JsonPathItem from; + JsonPathItem to; + int32 index; + int32 index_from; + int32 index_to; + bool range = jspGetArraySubscript(jsp, &from, &to, i); + + res = getArrayIndex(cxt, &from, jb, &index_from); - if (v == NULL) - continue; + if (jperIsError(res)) + break; - if (hasNext == true) + if (range) { - res = recursiveExecute(cxt, &elem, v, found); + res = getArrayIndex(cxt, &to, jb, &index_to); - if (jperIsError(res) || found == NULL) + if (jperIsError(res)) break; - - if (res == jperOk && found == NULL) - break; } else + index_to = index_from; + + if (!cxt->lax && + (index_from < 0 || + index_from > index_to || + index_to >= size)) { - res = jperOk; + res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + break; + } - if (found == NULL) - break; + if (index_from < 0) + index_from = 0; + + if (index_to >= size) + index_to = size - 1; - *found = lappend(*found, v); + res = jperNotFound; + + for (index = index_from; index <= index_to; index++) + { + JsonbValue *v = + getIthJsonbValueFromContainer(jb->val.binary.data, + (uint32) index); + + if (v == NULL) + continue; + + if (hasNext) + { + res = recursiveExecute(cxt, &elem, v, found); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + else + { + res = jperOk; + + if (!found) + break; + + *found = lappend(*found, v); + } } + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; } } else diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index a1bc2420ae..bb6200df97 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -175,7 +175,14 @@ makeIndexArray(List *list) v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems); foreach(cell, list) - v->value.array.elems[i++] = lfirst_int(cell); + { + JsonPathParseItem *jpi = lfirst(cell); + + Assert(jpi->type == jpiSubscript); + + v->value.array.elems[i].from = jpi->value.args.left; + v->value.array.elems[i++].to = jpi->value.args.right; + } return v; } @@ -222,10 +229,11 @@ makeAny(int first, int last) %type scalar_value path_primary expr pexpr array_accessor any_path accessor_op key predicate delimited_predicate + index_elem %type accessor_expr -%type index_elem index_list +%type index_list %type comp_op method @@ -329,22 +337,13 @@ expr: ; index_elem: - INT_P { $$ = list_make1_int(pg_atoi($1.val, 4, 0)); } - | INT_P TO_P INT_P { - int start = pg_atoi($1.val, 4, 0), - stop = pg_atoi($3.val, 4, 0), - i; - - $$ = NIL; - - for(i=start; i<= stop; i++) - $$ = lappend_int($$, i); - } + pexpr { $$ = makeItemBinary(jpiSubscript, $1, NULL); } + | pexpr TO_P pexpr { $$ = makeItemBinary(jpiSubscript, $1, $3); } ; index_list: - index_elem { $$ = $1; } - | index_list ',' index_elem { $$ = list_concat($1, $3); } + index_elem { $$ = list_make1($1); } + | index_list ',' index_elem { $$ = lappend($1, $3); } ; array_accessor: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index c11a3ab53a..f9f775dba1 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -78,6 +78,7 @@ typedef enum JsonPathItemType { jpiDouble, jpiDatetime, jpiKeyValue, + jpiSubscript, } JsonPathItemType; @@ -114,7 +115,10 @@ typedef struct JsonPathItem { /* storage for jpiIndexArray: indexes of array */ struct { int32 nelems; - int32 *elems; + struct { + int32 from; + int32 to; + } *elems; } array; /* jpiAny: levels */ @@ -139,6 +143,8 @@ extern void jspGetRightArg(JsonPathItem *v, JsonPathItem *a); extern Numeric jspGetNumeric(JsonPathItem *v); extern bool jspGetBool(JsonPathItem *v); extern char * jspGetString(JsonPathItem *v, int32 *len); +extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, + JsonPathItem *to, int i); /* * Parsing @@ -164,7 +170,11 @@ struct JsonPathParseItem { /* storage for jpiIndexArray: indexes of array */ struct { int nelems; - int32 *elems; + struct + { + JsonPathParseItem *from; + JsonPathParseItem *to; + } *elems; } array; /* jpiAny: levels */ diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index d39763273e..9634797a6d 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -76,12 +76,50 @@ select _jsonpath_exists(jsonb '[1]', '$.[1]'); f (1 row) +select _jsonpath_exists(jsonb '[1]', 'strict $.[1]'); + _jsonpath_exists +------------------ + +(1 row) + +select _jsonpath_query(jsonb '[1]', 'strict $[1]'); +ERROR: Invalid SQL/JSON subscript select _jsonpath_exists(jsonb '[1]', '$.[0]'); _jsonpath_exists ------------------ t (1 row) +select _jsonpath_exists(jsonb '[1]', '$.[0.3]'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[1]', '$.[0.5]'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[1]', '$.[0.9]'); + _jsonpath_exists +------------------ + t +(1 row) + +select _jsonpath_exists(jsonb '[1]', '$.[1.2]'); + _jsonpath_exists +------------------ + f +(1 row) + +select _jsonpath_exists(jsonb '[1]', 'strict $.[1.2]'); + _jsonpath_exists +------------------ + +(1 row) + select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); _jsonpath_exists ------------------ @@ -94,7 +132,7 @@ select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @. t (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'lax $ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); _jsonpath_exists ------------------ t @@ -124,6 +162,12 @@ select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); f (1 row) +select _jsonpath_exists(jsonb '[{"a": 1}, {"a": 2}]', '$[0 to 1] ? (@.a > 1)'); + _jsonpath_exists +------------------ + t +(1 row) + select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); _jsonpath_query ----------------- @@ -190,6 +234,14 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 1 13 (1 row) +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]', '$.[2.5 - 1 to @.size() - 2]'); + _jsonpath_query +----------------- + {"a": 13} + {"b": 14} + "ccc" +(3 rows) + select * from _jsonpath_query(jsonb '1', 'lax $[0]'); _jsonpath_query ----------------- @@ -408,79 +460,79 @@ select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? 1 (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? ( @ > 0)'); _jsonpath_exists ------------------ f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? ( @ > 0)'); _jsonpath_exists ------------------ f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? ( @ > 0)'); _jsonpath_exists ------------------ f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? ( @ > 0)'); _jsonpath_exists ------------------ t diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index c2cd893b60..47e4ff5dac 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -298,15 +298,21 @@ select '$.g ? (zip == $zip)'::jsonpath; (1 row) select '$.a.[1,2, 3 to 16]'::jsonpath; - jsonpath ------------------------------------------------ - $."a"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] + jsonpath +-------------------- + $."a"[1,2,3 to 16] (1 row) select '$.a[1,2, 3 to 16]'::jsonpath; - jsonpath ------------------------------------------------ - $."a"[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] + jsonpath +-------------------- + $."a"[1,2,3 to 16] +(1 row) + +select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; + jsonpath +---------------------------------------- + $."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)] (1 row) select 'null.type()'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index db53e86077..4741cb0fe2 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -8,18 +8,25 @@ select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{3}'); - select _jsonpath_exists(jsonb '[]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[*]'); select _jsonpath_exists(jsonb '[1]', '$.[1]'); +select _jsonpath_exists(jsonb '[1]', 'strict $.[1]'); +select _jsonpath_query(jsonb '[1]', 'strict $[1]'); select _jsonpath_exists(jsonb '[1]', '$.[0]'); +select _jsonpath_exists(jsonb '[1]', '$.[0.3]'); +select _jsonpath_exists(jsonb '[1]', '$.[0.5]'); +select _jsonpath_exists(jsonb '[1]', '$.[0.9]'); +select _jsonpath_exists(jsonb '[1]', '$.[1.2]'); +select _jsonpath_exists(jsonb '[1]', 'strict $.[1.2]'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'lax $ ? (@.a[*] >= @.b[*])'); +select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'strict $ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); +select _jsonpath_exists(jsonb '[{"a": 1}, {"a": 2}]', '$[0 to 1] ? (@.a > 1)'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); @@ -32,12 +39,14 @@ select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[1].a') select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[2].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0,1].a'); select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 10].a'); +select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]', '$.[2.5 - 1 to @.size() - 2]'); select * from _jsonpath_query(jsonb '1', 'lax $[0]'); select * from _jsonpath_query(jsonb '1', 'lax $[*]'); select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); + select * from _jsonpath_query(jsonb '{"a": 10}', '$'); select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); @@ -71,19 +80,19 @@ select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,}.b ? select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,2}.b ? (@ > 0)'); select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? ( @ > 0)'); +select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? ( @ > 0)'); select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 37e3e31a0a..af301e2474 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -54,6 +54,7 @@ select '$a[*]'::jsonpath; select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; +select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; select 'null.type()'::jsonpath; select '1.type()'::jsonpath; From 2fea72bd17ba4b1847d91141c871d8197c5e0d3c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 8 Mar 2017 00:07:40 +0300 Subject: [PATCH 35/97] Add jsonpath last subscript --- src/backend/utils/adt/jsonpath.c | 33 +++++++++++---- src/backend/utils/adt/jsonpath_exec.c | 44 ++++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 4 +- src/backend/utils/adt/jsonpath_scan.l | 1 + src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 33 +++++++++++++++ src/test/regress/expected/jsonpath.out | 32 ++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 8 +++- src/test/regress/sql/jsonpath.sql | 6 +++ 9 files changed, 152 insertions(+), 10 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 82c689414d..9517349a74 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -25,7 +25,7 @@ */ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, - bool forbiddenRoot) + bool forbiddenRoot, bool insideArraySubscript) { /* position from begining of jsonpath data */ int32 pos = buf->len - JSONPATH_HDRSZ; @@ -88,9 +88,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); - chld = flattenJsonPathParseItem(buf, item->value.args.left, forbiddenRoot); + chld = flattenJsonPathParseItem(buf, item->value.args.left, + forbiddenRoot, + insideArraySubscript); *(int32*)(buf->data + left) = chld; - chld = flattenJsonPathParseItem(buf, item->value.args.right, forbiddenRoot); + chld = flattenJsonPathParseItem(buf, item->value.args.right, + forbiddenRoot, + insideArraySubscript); *(int32*)(buf->data + right) = chld; } break; @@ -108,7 +112,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || - forbiddenRoot); + forbiddenRoot, + insideArraySubscript); *(int32*)(buf->data + arg) = chld; } break; @@ -129,6 +134,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("@ is not allowed in root expressions"))); break; + case jpiLast: + if (!insideArraySubscript) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("LAST is allowed only in array subscripts"))); + break; case jpiIndexArray: { int32 nelems = item->value.array.nelems; @@ -148,12 +159,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 frompos = flattenJsonPathParseItem(buf, item->value.array.elems[i].from, - true); + true, true); if (item->value.array.elems[i].to) topos = flattenJsonPathParseItem(buf, item->value.array.elems[i].to, - true); + true, true); else topos = 0; @@ -187,7 +198,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) *(int32*)(buf->data + next) = - flattenJsonPathParseItem(buf, item->next, forbiddenRoot); + flattenJsonPathParseItem(buf, item->next, forbiddenRoot, + insideArraySubscript); return pos; } @@ -211,7 +223,7 @@ jsonpath_in(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for jsonpath: \"%s\"", in))); - flattenJsonPathParseItem(&buf, jsonpath->expr, false); + flattenJsonPathParseItem(&buf, jsonpath->expr, false, false); res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); @@ -396,6 +408,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket Assert(!inKey); appendStringInfoChar(buf, '$'); break; + case jpiLast: + appendBinaryStringInfo(buf, "last", 4); + break; case jpiAnyArray: appendBinaryStringInfo(buf, "[*]", 3); break; @@ -559,6 +574,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiDouble: case jpiDatetime: case jpiKeyValue: + case jpiLast: break; case jpiKey: case jpiString: @@ -642,6 +658,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiExists || v->type == jpiRoot || v->type == jpiVariable || + v->type == jpiLast || v->type == jpiType || v->type == jpiSize || v->type == jpiAbs || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a765d25583..666e440465 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -25,6 +25,7 @@ typedef struct JsonPathExecContext { List *vars; bool lax; + int innermostArraySize; /* for LAST array index evaluation */ } JsonPathExecContext; static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, @@ -950,9 +951,12 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiIndexArray: if (JsonbType(jb) == jbvArray) { + int innermostArraySize = cxt->innermostArraySize; int i; int size = JsonbArraySize(jb); + cxt->innermostArraySize = size; /* for LAST evaluation */ + hasNext = jspGetNext(jsp, &elem); for (i = 0; i < jsp->content.array.nelems; i++) @@ -1032,10 +1036,49 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (res == jperOk && !found) break; } + + cxt->innermostArraySize = innermostArraySize; } else res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; + + case jpiLast: + { + JsonbValue tmpjbv; + JsonbValue *lastjbv; + int last; + bool hasNext; + + if (cxt->innermostArraySize < 0) + elog(ERROR, + "evaluating jsonpath LAST outside of array subscript"); + + hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found) + { + res = jperOk; + break; + } + + last = cxt->innermostArraySize - 1; + + lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv)); + + lastjbv->type = jbvNumeric; + lastjbv->val.numeric = DatumGetNumeric(DirectFunctionCall1( + int4_numeric, Int32GetDatum(last))); + + if (hasNext) + res = recursiveExecute(cxt, &elem, lastjbv, found); + else + { + res = jperOk; + *found = lappend(*found, lastjbv); + } + } + break; case jpiAnyKey: if (JsonbType(jb) == jbvObject) { @@ -1553,6 +1596,7 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) cxt.vars = vars; cxt.lax = (path->header & JSONPATH_LAX) != 0; + cxt.innermostArraySize = -1; if (!cxt.lax && !foundJson) { diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index bb6200df97..c300d47944 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -221,7 +221,7 @@ makeAny(int first, int last) %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P -%token ANY_P STRICT_P LAX_P +%token ANY_P STRICT_P LAX_P LAST_P %token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P %token KEYVALUE_P @@ -312,6 +312,7 @@ path_primary: scalar_value { $$ = $1; } | '$' { $$ = makeItemType(jpiRoot); } | '@' { $$ = makeItemType(jpiCurrent); } + | LAST_P { $$ = makeItemType(jpiLast); } ; accessor_expr: @@ -394,6 +395,7 @@ key_name: | CEILING_P | DATETIME_P | KEYVALUE_P + | LAST_P ; method: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 0929c8eac7..21885ec0fb 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -273,6 +273,7 @@ static keyword keywords[] = { { 2, false, TO_P, "to"}, { 3, false, ABS_P, "abs"}, { 3, false, LAX_P, "lax"}, + { 4, false, LAST_P, "last"}, { 4, true, NULL_P, "null"}, { 4, false, SIZE_P, "size"}, { 4, true, TRUE_P, "true"}, diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index f9f775dba1..c5f2a4872d 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -79,6 +79,7 @@ typedef enum JsonPathItemType { jpiDatetime, jpiKeyValue, jpiSubscript, + jpiLast, } JsonPathItemType; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 9634797a6d..ece1c8ba43 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -274,6 +274,39 @@ select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); 3 (3 rows) +select * from _jsonpath_query(jsonb '[]', '$[last]'); + _jsonpath_query +----------------- +(0 rows) + +select * from _jsonpath_query(jsonb '[]', 'strict $[last]'); +ERROR: Invalid SQL/JSON subscript +select * from _jsonpath_query(jsonb '[1]', '$[last]'); + _jsonpath_query +----------------- + 1 +(1 row) + +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]'); + _jsonpath_query +----------------- + 3 +(1 row) + +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]'); + _jsonpath_query +----------------- + 2 +(1 row) + +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]'); + _jsonpath_query +----------------- + 3 +(1 row) + +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]'); +ERROR: Invalid SQL/JSON subscript select * from _jsonpath_query(jsonb '{"a": 10}', '$'); _jsonpath_query ----------------- diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 47e4ff5dac..2afd41427d 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -315,6 +315,38 @@ select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; $."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)] (1 row) +select 'last'::jsonpath; +ERROR: LAST is allowed only in array subscripts +LINE 1: select 'last'::jsonpath; + ^ +select '"last"'::jsonpath; + jsonpath +---------- + "last" +(1 row) + +select '$.last'::jsonpath; + jsonpath +---------- + $."last" +(1 row) + +select '$ ? (last > 0)'::jsonpath; +ERROR: LAST is allowed only in array subscripts +LINE 1: select '$ ? (last > 0)'::jsonpath; + ^ +select '$[last]'::jsonpath; + jsonpath +---------- + $[last] +(1 row) + +select '$[@ ? (last > 0)]'::jsonpath; + jsonpath +----------------- + $[@?(last > 0)] +(1 row) + select 'null.type()'::jsonpath; jsonpath ------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 4741cb0fe2..76e169ca24 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -45,7 +45,13 @@ select * from _jsonpath_query(jsonb '1', 'lax $[*]'); select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); - +select * from _jsonpath_query(jsonb '[]', '$[last]'); +select * from _jsonpath_query(jsonb '[]', 'strict $[last]'); +select * from _jsonpath_query(jsonb '[1]', '$[last]'); +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]'); +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]'); +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]'); +select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]'); select * from _jsonpath_query(jsonb '{"a": 10}', '$'); select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index af301e2474..cc964351a5 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -55,6 +55,12 @@ select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; +select 'last'::jsonpath; +select '"last"'::jsonpath; +select '$.last'::jsonpath; +select '$ ? (last > 0)'::jsonpath; +select '$[last]'::jsonpath; +select '$[@ ? (last > 0)]'::jsonpath; select 'null.type()'::jsonpath; select '1.type()'::jsonpath; From 52306d5695aa0bce9016f91580272261a066ee2f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Mar 2017 13:30:20 +0300 Subject: [PATCH 36/97] Add jsonpath STARTS WITH predicate --- src/backend/utils/adt/jsonpath.c | 15 +++- src/backend/utils/adt/jsonpath_exec.c | 74 ++++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 21 +++--- src/backend/utils/adt/jsonpath_scan.l | 2 + src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 48 +++++++++++++ src/test/regress/expected/jsonpath.out | 12 ++++ src/test/regress/sql/jsonb_jsonpath.sql | 9 +++ src/test/regress/sql/jsonpath.sql | 3 + 9 files changed, 174 insertions(+), 11 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 9517349a74..a7a04cf1e2 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -75,6 +75,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiMul: case jpiDiv: case jpiMod: + case jpiStartsWith: { int32 left, right; @@ -265,6 +266,8 @@ printOperation(StringInfo buf, JsonPathItemType type) appendBinaryStringInfo(buf, " / ", 3); break; case jpiMod: appendBinaryStringInfo(buf, " % ", 3); break; + case jpiStartsWith: + appendBinaryStringInfo(buf, " starts with ", 13); break; default: elog(ERROR, "Unknown jsonpath item type: %d", type); } @@ -285,6 +288,7 @@ operationPriority(JsonPathItemType op) case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: + case jpiStartsWith: return 2; case jpiAdd: case jpiSub: @@ -350,6 +354,7 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket case jpiMul: case jpiDiv: case jpiMod: + case jpiStartsWith: if (printBracketes) appendStringInfoChar(buf, '('); jspGetLeftArg(v, &elem); @@ -598,6 +603,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: + case jpiStartsWith: read_int32(v->content.args.left, base, pos); read_int32(v->content.args.right, base, pos); break; @@ -666,7 +672,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiCeiling || v->type == jpiDouble || v->type == jpiDatetime || - v->type == jpiKeyValue + v->type == jpiKeyValue || + v->type == jpiStartsWith ); if (a) @@ -693,7 +700,8 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiSub || v->type == jpiMul || v->type == jpiDiv || - v->type == jpiMod + v->type == jpiMod || + v->type == jpiStartsWith ); jspInitByBuffer(a, v->base, v->content.args.left); @@ -715,7 +723,8 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiSub || v->type == jpiMul || v->type == jpiDiv || - v->type == jpiMod + v->type == jpiMod || + 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 666e440465..de669eff7d 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -768,6 +768,77 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return jperOk; } +static JsonPathExecResult +executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb) +{ + JsonPathExecResult res; + JsonPathItem elem; + List *lseq = NIL; + List *rseq = NIL; + ListCell *lc; + JsonbValue *initial; + JsonbValue initialbuf; + bool error = false; + bool found = false; + + jspGetRightArg(jsp, &elem); + res = recursiveExecute(cxt, &elem, jb, &rseq); + if (jperIsError(res)) + return jperError; + + if (list_length(rseq) != 1) + return jperError; + + initial = linitial(rseq); + + if (JsonbType(initial) == jbvScalar) + initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf); + + if (initial->type != jbvString) + return jperError; + + jspGetLeftArg(jsp, &elem); + res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); + if (jperIsError(res)) + return jperError; + + foreach(lc, lseq) + { + JsonbValue *whole = lfirst(lc); + JsonbValue wholebuf; + + if (JsonbType(whole) == jbvScalar) + whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf); + + if (whole->type != jbvString) + { + if (!cxt->lax) + return jperError; + + error = true; + } + else if (whole->val.string.len >= initial->val.string.len && + !memcmp(whole->val.string.val, + initial->val.string.val, + initial->val.string.len)) + { + if (cxt->lax) + return jperOk; + + found = true; + } + } + + if (found) /* possible only in strict mode */ + return jperOk; + + if (error) /* possible only in lax mode */ + return jperError; + + return jperNotFound; +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1464,6 +1535,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } break; + case jpiStartsWith: + res = executeStartsWithPredicate(cxt, jsp, jb); + 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 c300d47944..09fbe2e098 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -221,7 +221,7 @@ makeAny(int first, int last) %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P -%token ANY_P STRICT_P LAX_P LAST_P +%token ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P %token ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P %token KEYVALUE_P @@ -229,7 +229,7 @@ makeAny(int first, int last) %type scalar_value path_primary expr pexpr array_accessor any_path accessor_op key predicate delimited_predicate - index_elem + index_elem starts_with_initial %type accessor_expr @@ -299,15 +299,18 @@ predicate: | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); } | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); } | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } -/* - Left for the future - | pexpr LIKE_REGEX pattern { $$ = ...; } - | pexpr STARTS WITH STRING_P { $$ = ...; } - | pexpr STARTS WITH '$' STRING_P { $$ = ...; } - | pexpr STARTS WITH '$' STRING_P { $$ = ...; } + | pexpr STARTS_P WITH_P starts_with_initial + { $$ = makeItemBinary(jpiStartsWith, $1, $4); } +/* Left for the future (needs XQuery support) + | pexpr LIKE_REGEX pattern [FLAG_P flags] { $$ = ...; }; */ ; +starts_with_initial: + STRING_P { $$ = makeItemString(&$1); } + | VARIABLE_P { $$ = makeItemVariable(&$1); } + ; + path_primary: scalar_value { $$ = $1; } | '$' { $$ = makeItemType(jpiRoot); } @@ -396,6 +399,8 @@ key_name: | DATETIME_P | KEYVALUE_P | LAST_P + | STARTS_P + | WITH_P ; method: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 21885ec0fb..3f320ed4c4 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -278,10 +278,12 @@ static keyword keywords[] = { { 4, false, SIZE_P, "size"}, { 4, true, TRUE_P, "true"}, { 4, false, TYPE_P, "type"}, + { 4, false, WITH_P, "with"}, { 5, true, FALSE_P, "false"}, { 5, false, FLOOR_P, "floor"}, { 6, false, DOUBLE_P, "double"}, { 6, false, EXISTS_P, "exists"}, + { 6, false, STARTS_P, "starts"}, { 6, false, STRICT_P, "strict"}, { 7, false, CEILING_P, "ceiling"}, { 7, false, UNKNOWN_P, "unknown"}, diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index c5f2a4872d..18c3e6fcd2 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -80,6 +80,7 @@ typedef enum JsonPathItemType { jpiKeyValue, jpiSubscript, jpiLast, + jpiStartsWith, } JsonPathItemType; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index ece1c8ba43..2d94d30d9c 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -968,3 +968,51 @@ select _jsonpath_query(jsonb '"1.23"', '$.double()'); select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); ERROR: Non-numeric SQL/JSON item +select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); + _jsonpath_query +----------------- + "abc" + "abcabc" +(2 rows) + +select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); + _jsonpath_query +---------------------------- + ["", "a", "abc", "abcabc"] +(1 row) + +select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); + _jsonpath_query +---------------------------- + ["abc", "abcabc", null, 1] +(1 row) + +select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); + _jsonpath_query +---------------------------- + [null, 1, "abc", "abcabc"] +(1 row) + +select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); + _jsonpath_query +---------------------------- + [null, 1, "abd", "abdabc"] +(1 row) + +select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); + _jsonpath_query +----------------- + null + 1 +(2 rows) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 2afd41427d..b297d193ca 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -377,6 +377,18 @@ select 'true.type()'::jsonpath; true.type() (1 row) +select '$ ? (@ starts with "abc")'::jsonpath; + jsonpath +------------------------- + $?(@ starts with "abc") +(1 row) + +select '$ ? (@ starts with $var)'::jsonpath; + jsonpath +-------------------------- + $?(@ starts with $"var") +(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 76e169ca24..c2e88f19d0 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -194,3 +194,12 @@ select _jsonpath_query(jsonb '{}', '$.double()'); select _jsonpath_query(jsonb '1.23', '$.double()'); select _jsonpath_query(jsonb '"1.23"', '$.double()'); select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); + +select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); +select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); +select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); +select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); +select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); +select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); +select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); +select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index cc964351a5..34d2ac9055 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -68,6 +68,9 @@ select '"aaa".type()'::jsonpath; select 'aaa.type()'::jsonpath; select 'true.type()'::jsonpath; +select '$ ? (@ starts with "abc")'::jsonpath; +select '$ ? (@ starts with $var)'::jsonpath; + select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; select '$ ? (a < +1)'::jsonpath; From d46fc63b9be2620d462ba85363b65ecdd941f863 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 6 Sep 2017 03:21:37 +0300 Subject: [PATCH 37/97] Add jsonpath LIKE_REGEX predicate --- src/backend/utils/adt/jsonpath.c | 61 +++++++++++++++++ src/backend/utils/adt/jsonpath_exec.c | 70 ++++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 59 +++++++++++++++-- src/backend/utils/adt/jsonpath_scan.l | 2 + src/backend/utils/adt/regexp.c | 4 +- src/include/regex/regex.h | 5 ++ src/include/utils/jsonpath.h | 20 ++++++ src/test/regress/expected/jsonb_jsonpath.out | 15 +++++ src/test/regress/expected/jsonpath.out | 45 +++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 3 + src/test/regress/sql/jsonpath.sql | 9 +++ 11 files changed, 287 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index a7a04cf1e2..429c66673c 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -99,6 +99,29 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, *(int32*)(buf->data + right) = chld; } break; + case jpiLikeRegex: + { + int32 offs; + + appendBinaryStringInfo(buf, + (char *) &item->value.like_regex.flags, + sizeof(item->value.like_regex.flags)); + offs = buf->len; + appendBinaryStringInfo(buf, (char *) &offs /* fake value */, sizeof(offs)); + + appendBinaryStringInfo(buf, + (char *) &item->value.like_regex.patternlen, + sizeof(item->value.like_regex.patternlen)); + appendBinaryStringInfo(buf, item->value.like_regex.pattern, + item->value.like_regex.patternlen); + appendStringInfoChar(buf, '\0'); + + chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, + forbiddenRoot, + insideArraySubscript); + *(int32 *)(buf->data + offs) = chld; + } + break; case jpiFilter: case jpiIsUnknown: case jpiNot: @@ -369,6 +392,38 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket if (printBracketes) appendStringInfoChar(buf, ')'); break; + case jpiLikeRegex: + if (printBracketes) + appendStringInfoChar(buf, '('); + + jspInitByBuffer(&elem, v->base, v->content.like_regex.expr); + printJsonPathItem(buf, &elem, false, + operationPriority(elem.type) <= + operationPriority(v->type)); + + appendBinaryStringInfo(buf, " like_regex ", 12); + + escape_json(buf, v->content.like_regex.pattern); + + if (v->content.like_regex.flags) + { + appendBinaryStringInfo(buf, " flag \"", 7); + + if (v->content.like_regex.flags & JSP_REGEX_ICASE) + appendStringInfoChar(buf, 'i'); + if (v->content.like_regex.flags & JSP_REGEX_SLINE) + appendStringInfoChar(buf, 's'); + if (v->content.like_regex.flags & JSP_REGEX_MLINE) + appendStringInfoChar(buf, 'm'); + if (v->content.like_regex.flags & JSP_REGEX_WSPACE) + appendStringInfoChar(buf, 'x'); + + appendStringInfoChar(buf, '"'); + } + + if (printBracketes) + appendStringInfoChar(buf, ')'); + break; case jpiPlus: case jpiMinus: if (printBracketes) @@ -607,6 +662,12 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) read_int32(v->content.args.left, base, pos); read_int32(v->content.args.right, base, pos); break; + case jpiLikeRegex: + read_int32(v->content.like_regex.flags, base, pos); + read_int32(v->content.like_regex.expr, base, pos); + read_int32(v->content.like_regex.patternlen, base, pos); + v->content.like_regex.pattern = base + pos; + break; case jpiNot: case jpiExists: case jpiIsUnknown: diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index de669eff7d..2c260318a6 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -16,6 +16,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "lib/stringinfo.h" +#include "regex/regex.h" #include "utils/builtins.h" #include "utils/json.h" #include "utils/jsonpath.h" @@ -839,6 +840,72 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperNotFound; } +static JsonPathExecResult +executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb) +{ + JsonPathExecResult res; + JsonPathItem elem; + List *lseq = NIL; + ListCell *lc; + text *regex; + uint32 flags = jsp->content.like_regex.flags; + int cflags = REG_ADVANCED; + bool error = false; + bool found = false; + + if (flags & JSP_REGEX_ICASE) + cflags |= REG_ICASE; + if (flags & JSP_REGEX_MLINE) + cflags |= REG_NEWLINE; + if (flags & JSP_REGEX_SLINE) + cflags &= ~REG_NEWLINE; + if (flags & JSP_REGEX_WSPACE) + cflags |= REG_EXPANDED; + + regex = cstring_to_text_with_len(jsp->content.like_regex.pattern, + jsp->content.like_regex.patternlen); + + jspInitByBuffer(&elem, jsp->base, jsp->content.like_regex.expr); + res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); + if (jperIsError(res)) + return jperError; + + foreach(lc, lseq) + { + JsonbValue *str = lfirst(lc); + JsonbValue strbuf; + + if (JsonbType(str) == jbvScalar) + str = JsonbExtractScalar(str->val.binary.data, &strbuf); + + if (str->type != jbvString) + { + if (!cxt->lax) + return jperError; + + error = true; + } + else if (RE_compile_and_execute(regex, str->val.string.val, + str->val.string.len, cflags, + DEFAULT_COLLATION_OID, 0, NULL)) + { + if (cxt->lax) + return jperOk; + + found = true; + } + } + + if (found) /* possible only in strict mode */ + return jperOk; + + if (error) /* possible only in lax mode */ + return jperError; + + return jperNotFound; +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1538,6 +1605,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiStartsWith: res = executeStartsWithPredicate(cxt, jsp, jb); break; + case jpiLikeRegex: + res = executeLikeRegexPredicate(cxt, jsp, jb); + 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 09fbe2e098..cb264fcf59 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -15,7 +15,9 @@ #include "postgres.h" #include "fmgr.h" +#include "catalog/pg_collation.h" #include "nodes/pg_list.h" +#include "regex/regex.h" #include "utils/builtins.h" #include "utils/jsonpath.h" @@ -198,6 +200,53 @@ makeAny(int first, int last) return v; } +static JsonPathParseItem * +makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) +{ + JsonPathParseItem *v = makeItemType(jpiLikeRegex); + int i; + int cflags = REG_ADVANCED; + + v->value.like_regex.expr = expr; + v->value.like_regex.pattern = pattern->val; + v->value.like_regex.patternlen = pattern->len; + v->value.like_regex.flags = 0; + + for (i = 0; flags && i < flags->len; i++) + { + switch (flags->val[i]) + { + case 'i': + v->value.like_regex.flags |= JSP_REGEX_ICASE; + cflags |= REG_ICASE; + break; + case 's': + v->value.like_regex.flags &= ~JSP_REGEX_MLINE; + v->value.like_regex.flags |= JSP_REGEX_SLINE; + cflags |= REG_NEWLINE; + break; + case 'm': + v->value.like_regex.flags &= ~JSP_REGEX_SLINE; + v->value.like_regex.flags |= JSP_REGEX_MLINE; + cflags &= ~REG_NEWLINE; + break; + case 'x': + v->value.like_regex.flags |= JSP_REGEX_WSPACE; + cflags |= REG_EXPANDED; + break; + default: + yyerror(NULL, "unrecognized flag of LIKE_REGEX predicate"); + break; + } + } + + /* check regex validity */ + (void) RE_compile_and_cache(cstring_to_text_with_len(pattern->val, pattern->len), + cflags, DEFAULT_COLLATION_OID); + + return v; +} + %} /* BISON Declarations */ @@ -221,7 +270,7 @@ makeAny(int first, int last) %token STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_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 +%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 DATETIME_P %token KEYVALUE_P @@ -301,9 +350,9 @@ predicate: | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } | pexpr STARTS_P WITH_P starts_with_initial { $$ = makeItemBinary(jpiStartsWith, $1, $4); } -/* Left for the future (needs XQuery support) - | pexpr LIKE_REGEX pattern [FLAG_P flags] { $$ = ...; }; -*/ + | pexpr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }; + | pexpr LIKE_REGEX_P STRING_P FLAG_P STRING_P + { $$ = makeItemLikeRegex($1, &$3, &$5); }; ; starts_with_initial: @@ -401,6 +450,8 @@ key_name: | LAST_P | STARTS_P | WITH_P + | LIKE_REGEX_P + | FLAG_P ; method: diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 3f320ed4c4..7389d0e7f2 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -273,6 +273,7 @@ static keyword keywords[] = { { 2, false, TO_P, "to"}, { 3, false, ABS_P, "abs"}, { 3, false, LAX_P, "lax"}, + { 4, false, FLAG_P, "flag"}, { 4, false, LAST_P, "last"}, { 4, true, NULL_P, "null"}, { 4, false, SIZE_P, "size"}, @@ -289,6 +290,7 @@ static keyword keywords[] = { { 7, false, UNKNOWN_P, "unknown"}, { 8, false, DATETIME_P, "datetime"}, { 8, false, KEYVALUE_P, "keyvalue"}, + { 10,false, LIKE_REGEX_P, "like_regex"}, }; static int diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c index 5025a449fb..dfe8993953 100644 --- a/src/backend/utils/adt/regexp.c +++ b/src/backend/utils/adt/regexp.c @@ -129,7 +129,7 @@ static Datum build_regexp_split_result(regexp_matches_ctx *splitctx); * Pattern is given in the database encoding. We internally convert to * an array of pg_wchar, which is what Spencer's regex package wants. */ -static regex_t * +regex_t * RE_compile_and_cache(text *text_re, int cflags, Oid collation) { int text_re_len = VARSIZE_ANY_EXHDR(text_re); @@ -335,7 +335,7 @@ RE_execute(regex_t *re, char *dat, int dat_len, * Both pattern and data are given in the database encoding. We internally * convert to array of pg_wchar which is what Spencer's regex package wants. */ -static bool +bool RE_compile_and_execute(text *text_re, char *dat, int dat_len, int cflags, Oid collation, int nmatch, regmatch_t *pmatch) diff --git a/src/include/regex/regex.h b/src/include/regex/regex.h index 27fdc09040..4b1e80ddd9 100644 --- a/src/include/regex/regex.h +++ b/src/include/regex/regex.h @@ -173,4 +173,9 @@ extern int pg_regprefix(regex_t *, pg_wchar **, size_t *); extern void pg_regfree(regex_t *); extern size_t pg_regerror(int, const regex_t *, char *, size_t); +extern regex_t *RE_compile_and_cache(text *text_re, int cflags, Oid collation); +extern bool RE_compile_and_execute(text *text_re, char *dat, int dat_len, + int cflags, Oid collation, + int nmatch, regmatch_t *pmatch); + #endif /* _REGEX_H_ */ diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 18c3e6fcd2..08a7e6e21d 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -81,8 +81,14 @@ typedef enum JsonPathItemType { jpiSubscript, jpiLast, jpiStartsWith, + jpiLikeRegex, } JsonPathItemType; +/* XQuery regex mode flags for LIKE_REGEX predicate */ +#define JSP_REGEX_ICASE 0x01 /* i flag, case insensitive */ +#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 */ /* * Support functions to parse/construct binary value. @@ -133,6 +139,13 @@ typedef struct JsonPathItem { char *data; /* for bool, numeric and string/key */ int32 datalen; /* filled only for string/key */ } value; + + struct { + int32 expr; + char *pattern; + int32 patternlen; + uint32 flags; + } like_regex; } content; } JsonPathItem; @@ -185,6 +198,13 @@ struct JsonPathParseItem { uint32 last; } anybounds; + struct { + JsonPathParseItem *expr; + char *pattern; /* could not be not null-terminated */ + uint32 patternlen; + uint32 flags; + } like_regex; + /* scalars */ Numeric numeric; bool boolean; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 2d94d30d9c..b492a01d17 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1016,3 +1016,18 @@ select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ start 1 (2 rows) +select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); + _jsonpath_query +----------------- + "abc" + "abdacb" +(2 rows) + +select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'); + _jsonpath_query +----------------- + "abc" + "aBdC" + "abdacb" +(3 rows) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index b297d193ca..25fb28b0fd 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -389,6 +389,51 @@ select '$ ? (@ starts with $var)'::jsonpath; $?(@ starts with $"var") (1 row) +select '$ ? (@ like_regex "(invalid pattern")'::jsonpath; +ERROR: invalid regular expression: parentheses () not balanced +LINE 1: select '$ ? (@ like_regex "(invalid pattern")'::jsonpath; + ^ +select '$ ? (@ like_regex "pattern")'::jsonpath; + jsonpath +---------------------------- + $?(@ like_regex "pattern") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "")'::jsonpath; + jsonpath +---------------------------- + $?(@ like_regex "pattern") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "i")'::jsonpath; + jsonpath +------------------------------------- + $?(@ like_regex "pattern" flag "i") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "is")'::jsonpath; + jsonpath +-------------------------------------- + $?(@ like_regex "pattern" flag "is") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath; + jsonpath +-------------------------------------- + $?(@ like_regex "pattern" flag "im") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath; + jsonpath +-------------------------------------- + $?(@ like_regex "pattern" flag "sx") +(1 row) + +select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; +ERROR: bad jsonpath representation +LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; + ^ +DETAIL: unrecognized flag of LIKE_REGEX predicate at or near """ select '$ ? (a < 1)'::jsonpath; jsonpath ------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index c2e88f19d0..e5b58d1864 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -203,3 +203,6 @@ select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] st select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); + +select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); +select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 34d2ac9055..a70260cdc0 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -71,6 +71,15 @@ select 'true.type()'::jsonpath; select '$ ? (@ starts with "abc")'::jsonpath; select '$ ? (@ starts with $var)'::jsonpath; +select '$ ? (@ like_regex "(invalid pattern")'::jsonpath; +select '$ ? (@ like_regex "pattern")'::jsonpath; +select '$ ? (@ like_regex "pattern" flag "")'::jsonpath; +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 "a")'::jsonpath; + select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; select '$ ? (a < +1)'::jsonpath; From 43f0547dd97711fce8b31a880825374f3dbdd3b1 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Mar 2017 18:42:09 +0300 Subject: [PATCH 38/97] Add jbvDatetime JsonbValue type --- src/backend/utils/adt/jsonb_util.c | 20 ++++++++++++++++++++ src/include/utils/jsonb.h | 16 +++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 3be900f8ee..2eaac6ef75 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -15,8 +15,11 @@ #include "access/hash.h" #include "catalog/pg_collation.h" +#include "catalog/pg_type.h" #include "miscadmin.h" #include "utils/builtins.h" +#include "utils/datetime.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"); } } @@ -1741,11 +1745,27 @@ 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); + len = strlen(buf); + appendToBuffer(buffer, buf, len); + + *jentry = JENTRY_ISSTRING | len; + } + break; + default: elog(ERROR, "invalid jsonb scalar type"); } } + /* * Compare two jbvString JsonbValue values, a and b. * diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 602490a35a..58b99002a4 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -238,7 +238,9 @@ enum jbvType jbvArray = 0x10, jbvObject, /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ - jbvBinary + jbvBinary, + /* Virtual types */ + jbvDatetime = 0x20, }; /* @@ -279,11 +281,19 @@ struct JsonbValue int len; JsonbContainer *data; } binary; /* Array or object, in on-disk format */ + + struct + { + Datum value; + Oid typid; + int32 typmod; + } 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. From 77408b87bbea3718a4c03d34be521b8c18ce8313 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Mar 2017 18:43:43 +0300 Subject: [PATCH 39/97] Add .datetime() jsonpath item method --- src/backend/utils/adt/jsonpath.c | 23 +- src/backend/utils/adt/jsonpath_exec.c | 304 +++++++++++- src/backend/utils/adt/jsonpath_gram.y | 10 +- src/test/regress/expected/jsonb_jsonpath.out | 472 +++++++++++++++++++ src/test/regress/expected/jsonpath.out | 12 + src/test/regress/sql/jsonb_jsonpath.sql | 139 ++++++ src/test/regress/sql/jsonpath.sql | 2 + 7 files changed, 948 insertions(+), 14 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 429c66673c..a952258044 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -122,6 +122,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, *(int32 *)(buf->data + offs) = chld; } break; + case jpiDatetime: + if (!item->value.arg) + { + int32 arg = 0; + + appendBinaryStringInfo(buf, (char *) &arg, sizeof(arg)); + break; + } + /* fall through */ case jpiFilter: case jpiIsUnknown: case jpiNot: @@ -213,7 +222,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFloor: case jpiCeiling: case jpiDouble: - case jpiDatetime: case jpiKeyValue: break; default: @@ -536,7 +544,13 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket appendBinaryStringInfo(buf, ".double()", 9); break; case jpiDatetime: - appendBinaryStringInfo(buf, ".datetime()", 11); + appendBinaryStringInfo(buf, ".datetime(", 10); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); break; case jpiKeyValue: appendBinaryStringInfo(buf, ".keyvalue()", 11); @@ -632,7 +646,6 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiFloor: case jpiCeiling: case jpiDouble: - case jpiDatetime: case jpiKeyValue: case jpiLast: break; @@ -674,6 +687,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiPlus: case jpiMinus: case jpiFilter: + case jpiDatetime: read_int32(v->content.arg, base, pos); break; case jpiIndexArray: @@ -699,7 +713,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiIsUnknown || v->type == jpiExists || v->type == jpiPlus || - v->type == jpiMinus + v->type == jpiMinus || + v->type == jpiDatetime ); jspInitByBuffer(a, v->base, v->content.arg); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 2c260318a6..5f1acb30f7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -18,6 +18,7 @@ #include "lib/stringinfo.h" #include "regex/regex.h" #include "utils/builtins.h" +#include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" #include "utils/varlena.h" @@ -142,6 +143,16 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) value->val.string.val = VARDATA_ANY(computedValue); value->val.string.len = VARSIZE_ANY_EXHDR(computedValue); break; + case DATEOID: + case TIMEOID: + case TIMETZOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + value->type = jbvDatetime; + value->val.datetime.typid = var->typid; + value->val.datetime.typmod = var->typmod; + value->val.datetime.value = computedValue; + break; case JSONBOID: { Jsonb *jb = DatumGetJsonbP(computedValue); @@ -256,13 +267,24 @@ JsonbTypeName(JsonbValue *jb) return "boolean"; case jbvNull: return "null"; - /* TODO - return "date"; - return "time without time zone"; - return "time with time zone"; - return "timestamp without time zone"; - return "timestamp with time zone"; - */ + case jbvDatetime: + switch (jb->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, "unknown jsonb value datetime type oid %d", + jb->val.datetime.typid); + } + return "unknown"; default: elog(ERROR, "Unknown jsonb value type: %d", jb->type); return "unknown"; @@ -298,6 +320,118 @@ compareNumeric(Numeric a, Numeric b) ); } +static int +compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, bool *error) +{ + PGFunction cmpfunc = NULL; + + switch (typid1) + { + case DATEOID: + switch (typid2) + { + case DATEOID: + cmpfunc = date_cmp; + break; + case TIMESTAMPOID: + cmpfunc = date_cmp_timestamp; + break; + case TIMESTAMPTZOID: + cmpfunc = date_cmp_timestamptz; + break; + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + case TIMEOID: + switch (typid2) + { + case TIMEOID: + cmpfunc = time_cmp; + break; + case TIMETZOID: + val1 = DirectFunctionCall1(time_timetz, val1); + cmpfunc = timetz_cmp; + break; + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + *error = true; + return 0; + } + break; + + case TIMETZOID: + switch (typid2) + { + case TIMEOID: + val2 = DirectFunctionCall1(time_timetz, val2); + 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: + cmpfunc = timestamp_cmp_date; + break; + case TIMESTAMPOID: + cmpfunc = timestamp_cmp; + break; + case TIMESTAMPTZOID: + cmpfunc = timestamp_cmp_timestamptz; + break; + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + case TIMESTAMPTZOID: + switch (typid2) + { + case DATEOID: + cmpfunc = timestamptz_cmp_date; + break; + case TIMESTAMPOID: + cmpfunc = timestamptz_cmp_timestamp; + break; + case TIMESTAMPTZOID: + cmpfunc = timestamp_cmp; + break; + case TIMEOID: + case TIMETZOID: + *error = true; + return 0; + } + break; + + default: + elog(ERROR, "unknown SQL/JSON datetime type oid: %d", typid1); + } + + if (!cmpfunc) + elog(ERROR, "unknown SQL/JSON datetime type oid: %d", typid2); + + *error = false; + + return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); +} + static JsonPathExecResult checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) { @@ -330,6 +464,21 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) case jbvNumeric: eq = (compareNumeric(jb1->val.numeric, jb2->val.numeric) == 0); break; + case jbvDatetime: + { + bool error; + + eq = compareDatetime(jb1->val.datetime.value, + jb1->val.datetime.typid, + jb2->val.datetime.value, + jb2->val.datetime.typid, + &error) == 0; + + if (error) + return jperError; + + break; + } default: elog(ERROR,"1Wrong state"); } @@ -369,6 +518,20 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) jb2->val.string.val, jb2->val.string.len, DEFAULT_COLLATION_OID); break; + case jbvDatetime: + { + bool error; + + cmp = compareDatetime(jb1->val.datetime.value, + jb1->val.datetime.typid, + jb2->val.datetime.value, + jb2->val.datetime.typid, + &error); + + if (error) + return jperError; + } + break; default: return jperError; } @@ -906,6 +1069,31 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperNotFound; } +static bool +tryToParseDatetime(const char *template, text *datetime, + Datum *value, Oid *typid, int32 *typmod) +{ + MemoryContext mcxt = CurrentMemoryContext; + bool ok = false; + + PG_TRY(); + { + *value = to_datetime(datetime, template, -1, true, typid, typmod); + ok = true; + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION) + PG_RE_THROW(); + + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + } + PG_END_TRY(); + + return ok; +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1519,7 +1707,107 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } break; case jpiDatetime: - /* TODO */ + { + JsonbValue jbvbuf; + Datum value; + text *datetime_txt; + Oid typid; + int32 typmod = -1; + bool hasNext; + + if (JsonbType(jb) == jbvScalar) + jb = JsonbExtractScalar(jb->val.binary.data, &jbvbuf); + + if (jb->type != jbvString) + { + res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + break; + } + + datetime_txt = cstring_to_text_with_len(jb->val.string.val, + jb->val.string.len); + + res = jperOk; + + if (jsp->content.arg) + { + text *template_txt; + char *template_str; + int template_len; + MemoryContext mcxt = CurrentMemoryContext; + + jspGetArg(jsp, &elem); + + if (elem.type != jpiString) + elog(ERROR, "invalid jsonpath item type for .datetime() argument"); + + template_str = jspGetString(&elem, &template_len); + template_txt = cstring_to_text_with_len(template_str, + template_len); + + PG_TRY(); + { + value = to_datetime(datetime_txt, + template_str, template_len, + false, + &typid, &typmod); + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) != + ERRCODE_DATA_EXCEPTION) + PG_RE_THROW(); + + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + + res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + } + PG_END_TRY(); + + pfree(template_txt); + } + else + { + if (!tryToParseDatetime("yyyy-mm-dd HH24:MI:SS TZH:TZM", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("yyyy-mm-dd HH24:MI:SS TZH", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("yyyy-mm-dd HH24:MI:SS", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("yyyy-mm-dd", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("HH24:MI:SS TZH:TZM", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("HH24:MI:SS TZH", + datetime_txt, &value, &typid, &typmod) && + !tryToParseDatetime("HH24:MI:SS", + datetime_txt, &value, &typid, &typmod)) + res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + } + + pfree(datetime_txt); + + 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; + + if (hasNext) + res = recursiveExecute(cxt, &elem, jb, found); + else + *found = lappend(*found, jb); + } break; case jpiKeyValue: if (JsonbType(jb) != jbvObject) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index cb264fcf59..c352d629ce 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -278,7 +278,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type scalar_value path_primary expr pexpr array_accessor any_path accessor_op key predicate delimited_predicate - index_elem starts_with_initial + index_elem starts_with_initial opt_datetime_template %type accessor_expr @@ -421,9 +421,16 @@ accessor_op: | '.' array_accessor { $$ = $2; } | '.' any_path { $$ = $2; } | '.' method '(' ')' { $$ = makeItemType($2); } + | '.' DATETIME_P '(' opt_datetime_template ')' + { $$ = makeItemUnary(jpiDatetime, $4); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } ; +opt_datetime_template: + STRING_P { $$ = makeItemString(&$1); } + | /* EMPTY */ { $$ = NULL; } + ; + key: key_name { $$ = makeItemKey(&$1); } ; @@ -461,7 +468,6 @@ method: | FLOOR_P { $$ = jpiFloor; } | DOUBLE_P { $$ = jpiDouble; } | CEILING_P { $$ = jpiCeiling; } - | DATETIME_P { $$ = jpiDatetime; } | KEYVALUE_P { $$ = jpiKeyValue; } ; %% diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index b492a01d17..591aba6be7 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1031,3 +1031,475 @@ select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' "abdacb" (3 rows) +select _jsonpath_query(jsonb 'null', '$.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb 'true', '$.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb '1', '$.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb '[]', '$.datetime()'); + _jsonpath_query +----------------- +(0 rows) + +select _jsonpath_query(jsonb '[]', 'strict $.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb '{}', '$.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb '""', '$.datetime()'); +ERROR: Invalid argument for SQL/JSON datetime function +select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy")'); + _jsonpath_query +----------------- + "2017-03-10" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); + _jsonpath_query +----------------- + "date" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); + _jsonpath_query +----------------- + "2017-03-10" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + _jsonpath_query +----------------- + "date" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); + _jsonpath_query +------------------------------- + "timestamp without time zone" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); + _jsonpath_query +---------------------------- + "timestamp with time zone" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime("HH24:MI:SS").type()'); + _jsonpath_query +-------------------------- + "time without time zone" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + _jsonpath_query +----------------------- + "time with time zone" +(1 row) + +set time zone '+00'; +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + _jsonpath_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-10T07:34:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-10T17:34:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + _jsonpath_query +----------------------------- + "2017-03-10T07:14:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + _jsonpath_query +----------------------------- + "2017-03-10T17:54:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); + _jsonpath_query +----------------- + "12:34:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00+00:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00+05:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00-05:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + _jsonpath_query +------------------ + "12:34:00+05:20" +(1 row) + +select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + _jsonpath_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone '+10'; +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); + _jsonpath_query +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-10T17:34:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); + _jsonpath_query +----------------------------- + "2017-03-11T03:34:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + _jsonpath_query +----------------------------- + "2017-03-10T17:14:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); + _jsonpath_query +----------------------------- + "2017-03-11T03:54:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); + _jsonpath_query +----------------- + "12:34:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00+10:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00+05:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); + _jsonpath_query +------------------ + "12:34:00-05:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); + _jsonpath_query +------------------ + "12:34:00+05:20" +(1 row) + +select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + _jsonpath_query +------------------ + "12:34:00-05:20" +(1 row) + +set time zone default; +select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime().type()'); + _jsonpath_query +----------------- + "date" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime()'); + _jsonpath_query +----------------- + "2017-03-10" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime().type()'); + _jsonpath_query +------------------------------- + "timestamp without time zone" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime()'); + _jsonpath_query +----------------------- + "2017-03-10T12:34:56" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime().type()'); + _jsonpath_query +---------------------------- + "timestamp with time zone" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime()'); + _jsonpath_query +----------------------------- + "2017-03-10T01:34:56-08:00" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); + _jsonpath_query +---------------------------- + "timestamp with time zone" +(1 row) + +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime()'); + _jsonpath_query +----------------------------- + "2017-03-10T01:24:56-08:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime().type()'); + _jsonpath_query +-------------------------- + "time without time zone" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime()'); + _jsonpath_query +----------------- + "12:34:56" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime().type()'); + _jsonpath_query +----------------------- + "time with time zone" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime()'); + _jsonpath_query +------------------ + "12:34:56+03:00" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime().type()'); + _jsonpath_query +----------------------- + "time with time zone" +(1 row) + +select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime()'); + _jsonpath_query +------------------ + "12:34:56+03:10" +(1 row) + +set time zone '+00'; +-- date comparison +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10" + "2017-03-10T00:00:00" + "2017-03-10T00:00:00+00:00" +(3 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10" + "2017-03-11" + "2017-03-10T00:00:00" + "2017-03-10T12:34:56" + "2017-03-10T00:00:00+00:00" +(5 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-09" + "2017-03-09T21:02:03+00:00" +(2 rows) + +-- time comparison +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +------------------ + "12:35:00" + "12:35:00+00:00" +(2 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +------------------ + "12:35:00" + "12:36:00" + "12:35:00+00:00" +(3 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +------------------ + "12:34:00" + "12:35:00+01:00" + "13:35:00+01:00" +(3 rows) + +-- timetz comparison +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +------------------ + "12:35:00+01:00" +(1 row) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_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 _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +------------------ + "12:34:00+01:00" + "12:35:00+02:00" + "10:35:00" +(3 rows) + +-- timestamp comparison +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:35:00+00:00" +(2 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:36:00" + "2017-03-10T12:35:00+00:00" + "2017-03-10T13:35:00+00:00" + "2017-03-11" +(5 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T12:34:00" + "2017-03-10T11:35:00+00:00" + "2017-03-10" +(3 rows) + +-- timestamptz comparison +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T11:35:00+00:00" + "2017-03-10T11:35:00" +(2 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T11:35:00+00:00" + "2017-03-10T11:36:00+00:00" + "2017-03-10T14:35:00+00:00" + "2017-03-10T11:35:00" + "2017-03-10T12:35:00" + "2017-03-11" +(6 rows) + +select _jsonpath_query(jsonb + '["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"))' +); + _jsonpath_query +----------------------------- + "2017-03-10T11:34:00+00:00" + "2017-03-10T10:35:00+00:00" + "2017-03-10T10:35:00" + "2017-03-10" +(4 rows) + +set time zone default; diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 25fb28b0fd..8286986a4e 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -377,6 +377,18 @@ select 'true.type()'::jsonpath; true.type() (1 row) +select '$.datetime()'::jsonpath; + jsonpath +-------------- + $.datetime() +(1 row) + +select '$.datetime("datetime template")'::jsonpath; + jsonpath +--------------------------------- + $.datetime("datetime template") +(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 e5b58d1864..c12e48cbdd 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -206,3 +206,142 @@ select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ start select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'); + +select _jsonpath_query(jsonb 'null', '$.datetime()'); +select _jsonpath_query(jsonb 'true', '$.datetime()'); +select _jsonpath_query(jsonb '1', '$.datetime()'); +select _jsonpath_query(jsonb '[]', '$.datetime()'); +select _jsonpath_query(jsonb '[]', 'strict $.datetime()'); +select _jsonpath_query(jsonb '{}', '$.datetime()'); +select _jsonpath_query(jsonb '""', '$.datetime()'); + +select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy")'); +select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime("HH24:MI:SS").type()'); +select _jsonpath_query(jsonb '"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); + +set time zone '+00'; + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone '+10'; + +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); +select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); +select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); + +set time zone default; + +select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime()'); +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"12:34:56"', '$.datetime()'); +select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime()'); +select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime().type()'); +select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime()'); + +set time zone '+00'; + +-- date comparison +select _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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 _jsonpath_query(jsonb + '["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; diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index a70260cdc0..9037ff8257 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -67,6 +67,8 @@ select '1.type()'::jsonpath; select '"aaa".type()'::jsonpath; select 'aaa.type()'::jsonpath; select 'true.type()'::jsonpath; +select '$.datetime()'::jsonpath; +select '$.datetime("datetime template")'::jsonpath; select '$ ? (@ starts with "abc")'::jsonpath; select '$ ? (@ starts with $var)'::jsonpath; From f0fe995f9bb00b693cdb84372879d71ced5019de Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 23 Mar 2017 01:27:58 +0300 Subject: [PATCH 40/97] Allow jsonpath parenthized expressions to be a base part of a path --- src/backend/utils/adt/jsonpath.c | 7 ++++ src/backend/utils/adt/jsonpath_exec.c | 40 ++++++++++++++++---- src/backend/utils/adt/jsonpath_gram.y | 24 +++++++----- src/test/regress/expected/jsonb_jsonpath.out | 12 ++++++ src/test/regress/expected/jsonpath.out | 30 +++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 3 ++ src/test/regress/sql/jsonpath.sql | 6 +++ 7 files changed, 106 insertions(+), 16 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index a952258044..4c469b2b37 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -741,6 +741,13 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiRoot || v->type == jpiVariable || v->type == jpiLast || + v->type == jpiAdd || + v->type == jpiSub || + v->type == jpiMul || + v->type == jpiDiv || + v->type == jpiMod || + v->type == jpiPlus || + v->type == jpiMinus || v->type == jpiType || v->type == jpiSize || v->type == jpiAbs || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 5f1acb30f7..582f3b7438 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -711,6 +711,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, Datum ldatum; Datum rdatum; Datum res; + bool hasNext; jspGetLeftArg(jsp, &elem); @@ -742,7 +743,9 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (rval->type != jbvNumeric) return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); - if (!found) + hasNext = jspGetNext(jsp, &elem); + + if (!found && !hasNext) return jperOk; ldatum = NumericGetDatum(lval->val.numeric); @@ -773,6 +776,9 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, lval->type = jbvNumeric; lval->val.numeric = DatumGetNumeric(res); + if (hasNext) + return recursiveExecute(cxt, &elem, lval, found); + *found = lappend(*found, lval); return jperOk; @@ -786,6 +792,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathItem elem; List *seq = NIL; ListCell *lc; + bool hasNext; jspGetArg(jsp, &elem); jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); @@ -795,6 +802,8 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jper = jperNotFound; + hasNext = jspGetNext(jsp, &elem); + foreach(lc, seq) { JsonbValue *val = lfirst(lc); @@ -805,12 +814,10 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (val->type == jbvNumeric) { - jper = jperOk; - - if (!found) - return jper; + if (!found && !hasNext) + return jperOk; } - else if (!found) + else if (!found && !hasNext) continue; /* skip non-numerics processing */ if (val->type != jbvNumeric) @@ -831,7 +838,26 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); } - *found = lappend(*found, val); + if (hasNext) + { + JsonPathExecResult jper2 = recursiveExecute(cxt, &elem, val, found); + + if (jperIsError(jper2)) + return jper2; + + if (jper2 == jperOk) + { + if (!found) + return jperOk; + jper = jperOk; + } + } + else + { + Assert(found); + *found = lappend(*found, val); + jper = jperOk; + } } return jper; diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index c352d629ce..fc725e9788 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -144,18 +144,23 @@ makeItemUnary(int type, JsonPathParseItem* a) } static JsonPathParseItem* -makeItemList(List *list) { - JsonPathParseItem *head, *end; - ListCell *cell; +makeItemList(List *list) +{ + JsonPathParseItem *head, *end; + ListCell *cell = list_head(list); - head = end = (JsonPathParseItem*)linitial(list); + head = end = (JsonPathParseItem *) lfirst(cell); - foreach(cell, list) - { - JsonPathParseItem *c = (JsonPathParseItem*)lfirst(cell); + if (!lnext(cell)) + return head; - if (c == head) - continue; + /* append items to the end of already existing list */ + while (end->next) + end = end->next; + + for_each_cell(cell, lnext(cell)) + { + JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell); end->next = c; end = c; @@ -370,6 +375,7 @@ path_primary: accessor_expr: path_primary { $$ = list_make1($1); } | '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); } + | '(' expr ')' accessor_op { $$ = list_make2($2, $4); } | accessor_expr accessor_op { $$ = lappend($1, $2); } ; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 591aba6be7..98dadfdfde 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -842,6 +842,18 @@ select _jsonpath_query(jsonb 'null', 'aaa.type()'); "string" (1 row) +select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10'); + _jsonpath_query +----------------- + 13 +(1 row) + +select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); + _jsonpath_query +----------------- + 4 +(1 row) + select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); ERROR: SQL/JSON array not found select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 8286986a4e..85fbdc2f8f 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -446,6 +446,36 @@ ERROR: bad jsonpath representation LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; ^ DETAIL: unrecognized flag of LIKE_REGEX predicate at or near """ +select '($).a.b'::jsonpath; + jsonpath +----------- + $."a"."b" +(1 row) + +select '($.a.b).c.d'::jsonpath; + jsonpath +------------------- + $."a"."b"."c"."d" +(1 row) + +select '($.a.b + -$.x.y).c.d'::jsonpath; + jsonpath +---------------------------------- + ($."a"."b" + -$."x"."y")."c"."d" +(1 row) + +select '(-+$.a.b).c.d'::jsonpath; + jsonpath +------------------------- + (-(+$."a"."b"))."c"."d" +(1 row) + +select '1 + ($.a.b + 2).c.d'::jsonpath; + jsonpath +------------------------------- + (1 + ($."a"."b" + 2)."c"."d") +(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 c12e48cbdd..898a9aeb42 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -170,6 +170,9 @@ select _jsonpath_query(jsonb 'null', '123.type()'); select _jsonpath_query(jsonb 'null', '"123".type()'); select _jsonpath_query(jsonb 'null', 'aaa.type()'); +select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10'); +select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); + select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 9037ff8257..8f1995dfa6 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -82,6 +82,12 @@ select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; +select '($).a.b'::jsonpath; +select '($.a.b).c.d'::jsonpath; +select '($.a.b + -$.x.y).c.d'::jsonpath; +select '(-+$.a.b).c.d'::jsonpath; +select '1 + ($.a.b + 2).c.d'::jsonpath; + select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; select '$ ? (a < +1)'::jsonpath; From 76f672b0ea171ba0f2815a4c1022f2c087b48941 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Mar 2017 13:41:27 +0300 Subject: [PATCH 41/97] Allow jsonpath $ everywhere --- src/backend/utils/adt/jsonpath.c | 18 +++++++----------- src/backend/utils/adt/jsonpath_exec.c | 20 +++++--------------- src/test/regress/expected/jsonb_jsonpath.out | 12 ++++++++++++ src/test/regress/expected/jsonpath.out | 12 ++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 2 ++ src/test/regress/sql/jsonpath.sql | 2 ++ 6 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 4c469b2b37..7f28fd61eb 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -25,7 +25,7 @@ */ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, - bool forbiddenRoot, bool insideArraySubscript) + bool allowCurrent, bool insideArraySubscript) { /* position from begining of jsonpath data */ int32 pos = buf->len - JSONPATH_HDRSZ; @@ -90,11 +90,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); chld = flattenJsonPathParseItem(buf, item->value.args.left, - forbiddenRoot, + allowCurrent, insideArraySubscript); *(int32*)(buf->data + left) = chld; chld = flattenJsonPathParseItem(buf, item->value.args.right, - forbiddenRoot, + allowCurrent, insideArraySubscript); *(int32*)(buf->data + right) = chld; } @@ -117,7 +117,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, appendStringInfoChar(buf, '\0'); chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, - forbiddenRoot, + allowCurrent, insideArraySubscript); *(int32 *)(buf->data + offs) = chld; } @@ -145,7 +145,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || - forbiddenRoot, + allowCurrent, insideArraySubscript); *(int32*)(buf->data + arg) = chld; } @@ -153,16 +153,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiNull: break; case jpiRoot: - if (forbiddenRoot) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("root is not allowed in expression"))); break; case jpiAnyArray: case jpiAnyKey: break; case jpiCurrent: - if (!forbiddenRoot) + if (!allowCurrent) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("@ is not allowed in root expressions"))); @@ -230,7 +226,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) *(int32*)(buf->data + next) = - flattenJsonPathParseItem(buf, item->next, forbiddenRoot, + flattenJsonPathParseItem(buf, item->next, allowCurrent, insideArraySubscript); return pos; diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 582f3b7438..d9ead197d2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -27,6 +27,7 @@ typedef struct JsonPathExecContext { List *vars; bool lax; + JsonbValue *root; /* for $ evaluation */ int innermostArraySize; /* for LAST array index evaluation */ } JsonPathExecContext; @@ -1229,6 +1230,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); } break; + case jpiRoot: + jb = cxt->root; + /* fall through */ case jpiCurrent: if (!jspGetNext(jsp, &elem)) { @@ -1491,19 +1495,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiPlus: case jpiMinus: res = executeUnaryArithmExpr(cxt, jsp, jb, found); - break; - case jpiRoot: - if (jspGetNext(jsp, &elem)) - { - res = recursiveExecute(cxt, &elem, jb, found); - } - else - { - res = jperOk; - if (found) - *found = lappend(*found, copyJsonbValue(jb)); - } - break; case jpiFilter: jspGetArg(jsp, &elem); @@ -2048,12 +2039,11 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) JsonPathItem jsp; JsonbValue jbv; - JsonbInitBinary(&jbv, json); - jspInit(&jsp, path); cxt.vars = vars; cxt.lax = (path->header & JSONPATH_LAX) != 0; + cxt.root = JsonbInitBinary(&jbv, json); cxt.innermostArraySize = -1; if (!cxt.lax && !foundJson) diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 98dadfdfde..7f9c175761 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -657,6 +657,12 @@ select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); t (1 row) +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? ($.c.a == .b)'); + _jsonpath_exists +------------------ + t +(1 row) + select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); _jsonpath_exists ------------------ @@ -765,6 +771,12 @@ select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); f (1 row) +select _jsonpath_exists(jsonb '1', '$ ? ($ > 0)'); + _jsonpath_exists +------------------ + t +(1 row) + -- unwrapping of operator arguments in lax mode select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); _jsonpath_query diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 85fbdc2f8f..c0509e97be 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -177,6 +177,12 @@ select '$.a/+-1'::jsonpath; ($."a" / -1) (1 row) +select '$.g ? ($.a == 1)'::jsonpath; + jsonpath +-------------------- + $."g"?($."a" == 1) +(1 row) + select '$.g ? (@ == 1)'::jsonpath; jsonpath ---------------- @@ -315,6 +321,12 @@ select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; $."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)] (1 row) +select '$.a[$.a.size() - 3]'::jsonpath; + jsonpath +------------------------- + $."a"[$."a".size() - 3] +(1 row) + select 'last'::jsonpath; ERROR: LAST is allowed only in array subscripts LINE 1: select 'last'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 898a9aeb42..3abe58a8a1 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -134,6 +134,7 @@ from select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$ ? (.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$ ? (.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); +select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? ($.c.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); @@ -153,6 +154,7 @@ select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); +select _jsonpath_exists(jsonb '1', '$ ? ($ > 0)'); -- unwrapping of operator arguments in lax mode select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 8f1995dfa6..f4e7fb8b9b 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -31,6 +31,7 @@ select '$-1'::jsonpath; select '$--+1'::jsonpath; select '$.a/+-1'::jsonpath; +select '$.g ? ($.a == 1)'::jsonpath; select '$.g ? (@ == 1)'::jsonpath; select '$.g ? (a == 1)'::jsonpath; select '$.g ? (.a == 1)'::jsonpath; @@ -55,6 +56,7 @@ select '$.g ? (zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; +select '$.a[$.a.size() - 3]'::jsonpath; select 'last'::jsonpath; select '"last"'::jsonpath; select '$.last'::jsonpath; From a2e341d8ef0f29f4927361ad072ef60de2f76221 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Mar 2017 18:05:41 +0300 Subject: [PATCH 42/97] Refactor jsonpath items appending to the resulting list --- src/backend/utils/adt/jsonpath.c | 2 +- src/backend/utils/adt/jsonpath_exec.c | 308 +++++++++----------------- src/include/utils/jsonpath.h | 2 + 3 files changed, 113 insertions(+), 199 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 7f28fd61eb..d32322a088 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -719,7 +719,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) bool jspGetNext(JsonPathItem *v, JsonPathItem *a) { - if (v->nextPos > 0) + if (jspHasNext(v)) { Assert( v->type == jpiString || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index d9ead197d2..349f64a46d 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -575,6 +575,33 @@ copyJsonbValue(JsonbValue *src) return dst; } +static inline JsonPathExecResult +recursiveExecuteNext(JsonPathExecContext *cxt, + JsonPathItem *cur, JsonPathItem *next, + JsonbValue *v, List **found, bool copy) +{ + JsonPathItem elem; + bool hasNext; + + if (!cur) + hasNext = next != NULL; + else if (next) + hasNext = jspHasNext(cur); + else + { + next = &elem; + hasNext = jspGetNext(cur, next); + } + + if (hasNext) + return recursiveExecute(cxt, next, v, found); + + if (found) + *found = lappend(*found, copy ? copyJsonbValue(v) : v); + + return jperOk; +} + static inline JsonPathExecResult recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found) @@ -777,12 +804,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, lval->type = jbvNumeric; lval->val.numeric = DatumGetNumeric(res); - if (hasNext) - return recursiveExecute(cxt, &elem, lval, found); - - *found = lappend(*found, lval); - - return jperOk; + return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false); } static JsonPathExecResult @@ -790,6 +812,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonPathExecResult jper; + JsonPathExecResult jper2; JsonPathItem elem; List *seq = NIL; ListCell *lc; @@ -808,10 +831,9 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, foreach(lc, seq) { JsonbValue *val = lfirst(lc); - JsonbValue valbuf; if (JsonbType(val) == jbvScalar) - val = JsonbExtractScalar(val->val.binary.data, &valbuf); + JsonbExtractScalar(val->val.binary.data, val); if (val->type == jbvNumeric) { @@ -824,8 +846,6 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (val->type != jbvNumeric) return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND); - val = copyJsonbValue(val); - switch (jsp->type) { case jpiPlus: @@ -839,24 +859,15 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); } - if (hasNext) - { - JsonPathExecResult jper2 = recursiveExecute(cxt, &elem, val, found); + jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false); - if (jperIsError(jper2)) - return jper2; + if (jperIsError(jper2)) + return jper2; - if (jper2 == jperOk) - { - if (!found) - return jperOk; - jper = jperOk; - } - } - else + if (jper2 == jperOk) { - Assert(found); - *found = lappend(*found, val); + if (!found) + return jperOk; jper = jperOk; } } @@ -900,25 +911,22 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (level >= first) { /* check expression */ - if (jsp) - { - res = recursiveExecute(cxt, jsp, &v, found); - if (res == jperOk && !found) - break; - } - else - { - res = jperOk; - if (!found) - break; - *found = lappend(*found, copyJsonbValue(&v)); - } + res = recursiveExecuteNext(cxt, NULL, jsp, &v, found, true); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; } if (level < last && v.type == jbvBinary) { res = recursiveAny(cxt, jsp, &v, found, level + 1, first, last); + if (jperIsError(res)) + break; + if (res == jperOk && found == NULL) break; } @@ -1204,19 +1212,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (v != NULL) { - if (jspGetNext(jsp, &elem)) - { - res = recursiveExecute(cxt, &elem, v, found); - pfree(v); - } - else - { - res = jperOk; - if (found) - *found = lappend(*found, v); - else - pfree(v); - } + res = recursiveExecuteNext(cxt, jsp, NULL, v, found, false); + + if (jspHasNext(jsp) || !found) + pfree(v); /* free value if it was not added to found list */ } else if (!cxt->lax) { @@ -1234,36 +1233,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = cxt->root; /* fall through */ case jpiCurrent: - if (!jspGetNext(jsp, &elem)) { - /* we are last in chain of node */ - res = jperOk; - if (found) - { - JsonbValue *v; + JsonbValue *v; + JsonbValue vbuf; + bool copy = true; - if (JsonbType(jb) == jbvScalar) - v = JsonbExtractScalar(jb->val.binary.data, - palloc(sizeof(*v))); + if (JsonbType(jb) == jbvScalar) + { + if (jspHasNext(jsp)) + v = &vbuf; else - v = copyJsonbValue(jb); + { + v = palloc(sizeof(*v)); + copy = false; + } - *found = lappend(*found, v); + JsonbExtractScalar(jb->val.binary.data, v); } - } - else if (JsonbType(jb) == jbvScalar) - { - JsonbValue v; - - JsonbExtractScalar(jb->val.binary.data, &v); + else + v = jb; - res = recursiveExecute(cxt, &elem, &v, found); - } - else - { - res = recursiveExecute(cxt, &elem, jb, found); + res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy); + break; } - break; case jpiAnyArray: if (JsonbType(jb) == jbvArray) { @@ -1278,25 +1270,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (r == WJB_ELEM) { - if (hasNext == true) - { - res = recursiveExecute(cxt, &elem, &v, found); - - if (jperIsError(res)) - break; - - if (res == jperOk && found == NULL) - break; - } - else - { - res = jperOk; + res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true); - if (found == NULL) - break; + if (jperIsError(res)) + break; - *found = lappend(*found, copyJsonbValue(&v)); - } + if (res == jperOk && !found) + break; } } } @@ -1365,25 +1345,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (v == NULL) continue; - if (hasNext) - { - res = recursiveExecute(cxt, &elem, v, found); - - if (jperIsError(res)) - break; + res = recursiveExecuteNext(cxt, jsp, &elem, v, found, false); - if (res == jperOk && !found) - break; - } - else - { - res = jperOk; - - if (!found) - break; + if (jperIsError(res)) + break; - *found = lappend(*found, v); - } + if (res == jperOk && !found) + break; } if (jperIsError(res)) @@ -1426,13 +1394,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, lastjbv->val.numeric = DatumGetNumeric(DirectFunctionCall1( int4_numeric, Int32GetDatum(last))); - if (hasNext) - res = recursiveExecute(cxt, &elem, lastjbv, found); - else - { - res = jperOk; - *found = lappend(*found, lastjbv); - } + res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext); } break; case jpiAnyKey: @@ -1449,25 +1411,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (r == WJB_VALUE) { - if (hasNext == true) - { - res = recursiveExecute(cxt, &elem, &v, found); - - if (jperIsError(res)) - break; + res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true); - if (res == jperOk && found == NULL) - break; - } - else - { - res = jperOk; - - if (found == NULL) - break; + if (jperIsError(res)) + break; - *found = lappend(*found, copyJsonbValue(&v)); - } + if (res == jperOk && !found) + break; } } } @@ -1501,10 +1451,8 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = recursiveExecute(cxt, &elem, jb, NULL); if (res != jperOk) res = jperNotFound; - else if (jspGetNext(jsp, &elem)) - res = recursiveExecute(cxt, &elem, jb, found); - else if (found) - *found = lappend(*found, copyJsonbValue(jb)); + else + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); break; case jpiAny: { @@ -1513,19 +1461,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, /* first try without any intermediate steps */ if (jsp->content.anybounds.first == 0) { - if (hasNext) - { - res = recursiveExecute(cxt, &elem, jb, found); - if (res == jperOk && !found) - break; - } - else - { - res = jperOk; - if (!found) + res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, true); + + if (res == jperOk && !found) break; - *found = lappend(*found, copyJsonbValue(jb)); - } } if (jb->type == jbvBinary) @@ -1559,21 +1498,22 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiNumeric: case jpiString: case jpiVariable: - if (jspGetNext(jsp, &elem)) { - JsonbValue jbv; - computeJsonPathItem(cxt, jsp, &jbv); - res = recursiveExecute(cxt, &elem, &jbv, found); - } - else - { - res = jperOk; - if (found) + JsonbValue vbuf; + JsonbValue *v; + bool hasNext = jspGetNext(jsp, &elem); + + if (!hasNext && !found) { - JsonbValue *jbv = palloc(sizeof(*jbv)); - computeJsonPathItem(cxt, jsp, jbv); - *found = lappend(*found, jbv); + res = jperOk; /* skip evaluation */ + break; } + + v = hasNext ? &vbuf : palloc(sizeof(*v)); + + computeJsonPathItem(cxt, jsp, v); + + res = recursiveExecuteNext(cxt, jsp, &elem, v, found, hasNext); } break; case jpiType: @@ -1584,12 +1524,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jbv->val.string.val = pstrdup(JsonbTypeName(jb)); jbv->val.string.len = strlen(jbv->val.string.val); - res = jperOk; - - if (jspGetNext(jsp, &elem)) - res = recursiveExecute(cxt, &elem, jbv, found); - else if (found) - *found = lappend(*found, jbv); + res = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false); } break; case jpiSize: @@ -1614,12 +1549,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(size))); - res = jperOk; - - if (jspGetNext(jsp, &elem)) - res = recursiveExecute(cxt, &elem, jb, found); - else if (found) - *found = lappend(*found, jb); + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false); } break; case jpiAbs: @@ -1655,12 +1585,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jb->type = jbvNumeric; jb->val.numeric = DatumGetNumeric(datum); - res = jperOk; - - if (jspGetNext(jsp, &elem)) - res = recursiveExecute(cxt, &elem, jb, found); - else if (found) - *found = lappend(*found, jb); + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false); } else res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); @@ -1715,12 +1640,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, PG_END_TRY(); if (res == jperOk) - { - if (jspGetNext(jsp, &elem)) - res = recursiveExecute(cxt, &elem, jb, found); - else if (found) - *found = lappend(*found, copyJsonbValue(jb)); - } + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); } break; case jpiDatetime: @@ -1820,10 +1740,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jb->val.datetime.typid = typid; jb->val.datetime.typmod = typmod; - if (hasNext) - res = recursiveExecute(cxt, &elem, jb, found); - else - *found = lappend(*found, jb); + res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext); } break; case jpiKeyValue: @@ -1891,18 +1808,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbInitBinary(&obj, jsonb); - if (hasNext) - { - res = recursiveExecute(cxt, &elem, &obj, found); + res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true); - if (jperIsError(res)) - break; + if (jperIsError(res)) + break; - if (res == jperOk && !found) - break; - } - else - *found = lappend(*found, copyJsonbValue(&obj)); + if (res == jperOk && !found) + break; } } } diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 08a7e6e21d..c0b172ed63 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -149,6 +149,8 @@ typedef struct JsonPathItem { } content; } JsonPathItem; +#define jspHasNext(jsp) ((jsp)->nextPos > 0) + extern void jspInit(JsonPathItem *v, JsonPath *js); extern void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos); extern bool jspGetNext(JsonPathItem *v, JsonPathItem *a); From 4ec2a285abe5a2c974048f13e82502c8e5e542cf Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 30 Mar 2017 16:33:39 +0300 Subject: [PATCH 43/97] Introduce JsonValueList for jsonpath --- src/backend/utils/adt/jsonpath_exec.c | 253 ++++++++++++++++++++------ src/include/utils/jsonpath.h | 8 +- 2 files changed, 200 insertions(+), 61 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 349f64a46d..5c35dc8ee2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -31,12 +31,123 @@ typedef struct JsonPathExecContext int innermostArraySize; /* for LAST array index evaluation */ } JsonPathExecContext; +typedef struct JsonValueListIterator +{ + ListCell *lcell; +} JsonValueListIterator; + +#define JsonValueListIteratorEnd ((ListCell *) -1) + static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found); + JsonValueList *found); static JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb, List **found); + JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); + +static inline JsonbValue *wrapItemsInArray(const JsonValueList *items); + + +static inline void +JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv) +{ + if (jvl->singleton) + { + jvl->list = list_make2(jvl->singleton, jbv); + jvl->singleton = NULL; + } + else if (!jvl->list) + jvl->singleton = jbv; + else + jvl->list = lappend(jvl->list, jbv); +} + +static inline void +JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2) +{ + if (jvl1->singleton) + { + if (jvl2.singleton) + jvl1->list = list_make2(jvl1->singleton, jvl2.singleton); + else + jvl1->list = lcons(jvl1->singleton, jvl2.list); + + jvl1->singleton = NULL; + } + else if (jvl2.singleton) + { + if (jvl1->list) + jvl1->list = lappend(jvl1->list, jvl2.singleton); + else + jvl1->singleton = jvl2.singleton; + } + else if (jvl1->list) + jvl1->list = list_concat(jvl1->list, jvl2.list); + else + jvl1->list = jvl2.list; +} + +static inline int +JsonValueListLength(const JsonValueList *jvl) +{ + return jvl->singleton ? 1 : list_length(jvl->list); +} + +static inline bool +JsonValueListIsEmpty(JsonValueList *jvl) +{ + return !jvl->singleton && list_length(jvl->list) <= 0; +} + +static inline JsonbValue * +JsonValueListHead(JsonValueList *jvl) +{ + return jvl->singleton ? jvl->singleton : linitial(jvl->list); +} + +static inline void +JsonValueListClear(JsonValueList *jvl) +{ + jvl->singleton = NULL; + jvl->list = NIL; +} + +static inline List * +JsonValueListGetList(JsonValueList *jvl) +{ + if (jvl->singleton) + return list_make1(jvl->singleton); + + return jvl->list; +} + +static inline JsonbValue * +JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) +{ + if (it->lcell == JsonValueListIteratorEnd) + return NULL; + + if (it->lcell) + it->lcell = lnext(it->lcell); + else + { + if (jvl->singleton) + { + it->lcell = JsonValueListIteratorEnd; + return jvl->singleton; + } + + it->lcell = list_head(jvl->list); + } + + if (!it->lcell) + { + it->lcell = JsonValueListIteratorEnd; + return NULL; + } + + return lfirst(it->lcell); +} static inline JsonbValue * JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) @@ -578,7 +689,7 @@ copyJsonbValue(JsonbValue *src) static inline JsonPathExecResult recursiveExecuteNext(JsonPathExecContext *cxt, JsonPathItem *cur, JsonPathItem *next, - JsonbValue *v, List **found, bool copy) + JsonbValue *v, JsonValueList *found, bool copy) { JsonPathItem elem; bool hasNext; @@ -597,35 +708,34 @@ recursiveExecuteNext(JsonPathExecContext *cxt, return recursiveExecute(cxt, next, v, found); if (found) - *found = lappend(*found, copy ? copyJsonbValue(v) : v); + JsonValueListAppend(found, copy ? copyJsonbValue(v) : v); return jperOk; } static inline JsonPathExecResult recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, List **found) + JsonbValue *jb, JsonValueList *found) { if (cxt->lax) { - List *seq = NIL; + JsonValueList seq = { 0 }; + JsonValueListIterator it = { 0 }; JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq); - ListCell *lc; + JsonbValue *item; if (jperIsError(res)) return res; - foreach(lc, seq) + while ((item = JsonValueListNext(&seq, &it))) { - JsonbValue *item = lfirst(lc); - if (item->type == jbvArray) { JsonbValue *elem = item->val.array.elems; JsonbValue *last = elem + item->val.array.nElems; for (; elem < last; elem++) - *found = lappend(*found, copyJsonbValue(elem)); + JsonValueListAppend(found, copyJsonbValue(elem)); } else if (item->type == jbvBinary && JsonContainerIsArray(item->val.binary.data)) @@ -637,11 +747,11 @@ recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, while ((tok = JsonbIteratorNext(&it, &elem, true)) != WJB_DONE) { if (tok == WJB_ELEM) - *found = lappend(*found, copyJsonbValue(&elem)); + JsonValueListAppend(found, copyJsonbValue(&elem)); } } else - *found = lappend(*found, item); + JsonValueListAppend(found, item); } return jperOk; @@ -655,10 +765,10 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { JsonPathExecResult res; JsonPathItem elem; - List *lseq = NIL; - List *rseq = NIL; - ListCell *llc; - ListCell *rlc; + JsonValueList lseq = { 0 }; + JsonValueList rseq = { 0 }; + JsonValueListIterator lseqit = { 0 }; + JsonbValue *lval; bool error = false; bool found = false; @@ -672,14 +782,13 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) if (jperIsError(res)) return jperError; - foreach(llc, lseq) + while ((lval = JsonValueListNext(&lseq, &lseqit))) { - JsonbValue *lval = lfirst(llc); + JsonValueListIterator rseqit = { 0 }; + JsonbValue *rval; - foreach(rlc, rseq) + while ((rval = JsonValueListNext(&rseq, &rseqit))) { - JsonbValue *rval = lfirst(rlc); - switch (jsp->type) { case jpiEqual: @@ -726,12 +835,12 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, List **found) + JsonbValue *jb, JsonValueList *found) { JsonPathExecResult jper; JsonPathItem elem; - List *lseq = NIL; - List *rseq = NIL; + JsonValueList lseq = { 0 }; + JsonValueList rseq = { 0 }; JsonbValue *lval; JsonbValue *rval; JsonbValue lvalbuf; @@ -752,10 +861,12 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */ } - if (jper != jperOk || list_length(lseq) != 1 || list_length(rseq) != 1) + if (jper != jperOk || + JsonValueListLength(&lseq) != 1 || + JsonValueListLength(&rseq) != 1) return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); - lval = linitial(lseq); + lval = JsonValueListHead(&lseq); if (JsonbType(lval) == jbvScalar) lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf); @@ -763,7 +874,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (lval->type != jbvNumeric) return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); - rval = linitial(rseq); + rval = JsonValueListHead(&rseq); if (JsonbType(rval) == jbvScalar) rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf); @@ -809,13 +920,14 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, List **found) + JsonbValue *jb, JsonValueList *found) { JsonPathExecResult jper; JsonPathExecResult jper2; JsonPathItem elem; - List *seq = NIL; - ListCell *lc; + JsonValueList seq = { 0 }; + JsonValueListIterator it = { 0 }; + JsonbValue *val; bool hasNext; jspGetArg(jsp, &elem); @@ -828,10 +940,8 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, hasNext = jspGetNext(jsp, &elem); - foreach(lc, seq) + while ((val = JsonValueListNext(&seq, &it))) { - JsonbValue *val = lfirst(lc); - if (JsonbType(val) == jbvScalar) JsonbExtractScalar(val->val.binary.data, val); @@ -880,7 +990,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found, uint32 level, uint32 first, uint32 last) + JsonValueList *found, uint32 level, uint32 first, uint32 last) { JsonPathExecResult res = jperNotFound; JsonbIterator *it; @@ -941,17 +1051,17 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, int32 *index) { JsonbValue *jbv; - List *found = NIL; + JsonValueList found = { 0 }; JsonbValue tmp; JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found); if (jperIsError(res)) return res; - if (list_length(found) != 1) + if (JsonValueListLength(&found) != 1) return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); - jbv = linitial(found); + jbv = JsonValueListHead(&found); if (JsonbType(jbv) == jbvScalar) jbv = JsonbExtractScalar(jbv->val.binary.data, &tmp); @@ -973,9 +1083,10 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, { JsonPathExecResult res; JsonPathItem elem; - List *lseq = NIL; - List *rseq = NIL; - ListCell *lc; + JsonValueList lseq = { 0 }; + JsonValueList rseq = { 0 }; + JsonValueListIterator lit = { 0 }; + JsonbValue *whole; JsonbValue *initial; JsonbValue initialbuf; bool error = false; @@ -986,10 +1097,10 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jperIsError(res)) return jperError; - if (list_length(rseq) != 1) + if (JsonValueListLength(&rseq) != 1) return jperError; - initial = linitial(rseq); + initial = JsonValueListHead(&rseq); if (JsonbType(initial) == jbvScalar) initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf); @@ -1002,9 +1113,8 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jperIsError(res)) return jperError; - foreach(lc, lseq) + while ((whole = JsonValueListNext(&lseq, &lit))) { - JsonbValue *whole = lfirst(lc); JsonbValue wholebuf; if (JsonbType(whole) == jbvScalar) @@ -1044,8 +1154,9 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, { JsonPathExecResult res; JsonPathItem elem; - List *lseq = NIL; - ListCell *lc; + JsonValueList seq = { 0 }; + JsonValueListIterator it = { 0 }; + JsonbValue *str; text *regex; uint32 flags = jsp->content.like_regex.flags; int cflags = REG_ADVANCED; @@ -1065,13 +1176,12 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jsp->content.like_regex.patternlen); jspInitByBuffer(&elem, jsp->base, jsp->content.like_regex.expr); - res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); + res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); if (jperIsError(res)) return jperError; - foreach(lc, lseq) + while ((str = JsonValueListNext(&seq, &it))) { - JsonbValue *str = lfirst(lc); JsonbValue strbuf; if (JsonbType(str) == jbvScalar) @@ -1141,7 +1251,7 @@ tryToParseDatetime(const char *template, text *datetime, */ static JsonPathExecResult recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, List **found) + JsonbValue *jb, JsonValueList *found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -1481,7 +1591,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = recursiveExecute(cxt, &elem, jb, NULL); else { - List *vals = NIL; + JsonValueList vals = { 0 }; /* * In strict mode we must get a complete list of values @@ -1490,7 +1600,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = recursiveExecute(cxt, &elem, jb, &vals); if (!jperIsError(res)) - res = list_length(vals) > 0 ? jperOk : jperNotFound; + res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; } break; case jpiNull: @@ -1834,7 +1944,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, static JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, List **found) + JsonbValue *jb, JsonValueList *found) { if (cxt->lax && JsonbType(jb) == jbvArray) { @@ -1907,7 +2017,7 @@ wrapItem(JsonbValue *jbv) static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found) + JsonValueList *found) { check_stack_depth(); @@ -1945,7 +2055,7 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, * Public interface to jsonpath executor */ JsonPathExecResult -executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) +executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson) { JsonPathExecContext cxt; JsonPathItem jsp; @@ -1964,13 +2074,13 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson) * In strict mode we must get a complete list of values to check * that there are no errors at all. */ - List *vals = NIL; + JsonValueList vals = { 0 }; JsonPathExecResult res = recursiveExecute(&cxt, &jsp, &jbv, &vals); if (jperIsError(res)) return res; - return list_length(vals) > 0 ? jperOk : jperNotFound; + return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; } return recursiveExecute(&cxt, &jsp, &jbv, foundJson); @@ -2155,7 +2265,7 @@ static Datum jsonb_jsonpath_query(FunctionCallInfo fcinfo) { FuncCallContext *funcctx; - List *found = NIL; + List *found; JsonbValue *v; ListCell *c; @@ -2166,6 +2276,7 @@ jsonb_jsonpath_query(FunctionCallInfo fcinfo) JsonPathExecResult res; MemoryContext oldcontext; List *vars = NIL; + JsonValueList found = { 0 }; funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -2181,7 +2292,7 @@ jsonb_jsonpath_query(FunctionCallInfo fcinfo) PG_FREE_IF_COPY(jp, 1); - funcctx->user_fctx = found; + funcctx->user_fctx = JsonValueListGetList(&found); MemoryContextSwitchTo(oldcontext); } @@ -2211,3 +2322,25 @@ jsonb_jsonpath_query3(PG_FUNCTION_ARGS) { return jsonb_jsonpath_query(fcinfo); } + +/* Construct a JSON array from the item list */ +static inline JsonbValue * +wrapItemsInArray(const JsonValueList *items) +{ + JsonbParseState *ps = NULL; + JsonValueListIterator it = { 0 }; + JsonbValue *jbv; + + pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL); + + while ((jbv = JsonValueListNext(items, &it))) + { + if (jbv->type == jbvBinary && + JsonContainerIsScalar(jbv->val.binary.data)) + JsonbExtractScalar(jbv->val.binary.data, jbv); + + pushJsonbValue(&ps, WJB_ELEM, jbv); + } + + return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); +} diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index c0b172ed63..6f56e85dbe 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -257,9 +257,15 @@ typedef struct JsonPathVariable { +typedef struct JsonValueList +{ + JsonbValue *singleton; + List *list; +} JsonValueList; + JsonPathExecResult executeJsonPath(JsonPath *path, List *vars, /* list of JsonPathVariable */ Jsonb *json, - List **foundJson); + JsonValueList *foundJson); #endif From c881be420708733a0f042a701e059a01b9d15bcf Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 14:02:18 +0300 Subject: [PATCH 44/97] Mark some jsonpath functions as inline --- src/backend/utils/adt/jsonpath_exec.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 5c35dc8ee2..8a42951dda 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -38,7 +38,7 @@ typedef struct JsonValueListIterator #define JsonValueListIteratorEnd ((ListCell *) -1) -static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, +static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); @@ -324,7 +324,7 @@ computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *va * as alias to jbvBinary */ #define jbvScalar jbvBinary -static int +static inline int JsonbType(JsonbValue *jb) { int type = jb->type; @@ -544,7 +544,7 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, bool *error) return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); } -static JsonPathExecResult +static inline JsonPathExecResult checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) { bool eq = false; @@ -1977,7 +1977,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, * Wrap a non-array SQL/JSON item into an array for applying array subscription * path steps in lax mode. */ -static JsonbValue * +static inline JsonbValue * wrapItem(JsonbValue *jbv) { JsonbParseState *ps = NULL; @@ -2015,12 +2015,10 @@ wrapItem(JsonbValue *jbv) return JsonbWrapInBinary(jbv, NULL); } -static JsonPathExecResult +static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - check_stack_depth(); - if (cxt->lax) { switch (jsp->type) From 0284c78751693e0c8a2bec139efe9d80cb0a2adb Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:28:51 +0300 Subject: [PATCH 45/97] Add jsonpath support for JsonbValue objects and arrays --- src/backend/utils/adt/jsonpath_exec.c | 149 +++++++++++++++++++------- 1 file changed, 109 insertions(+), 40 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 8a42951dda..932bc67e81 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -557,9 +557,6 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) return jperError; } - if (jb1->type == jbvBinary) - return jperError; - switch (jb1->type) { case jbvNull: @@ -591,8 +588,14 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) break; } + + case jbvBinary: + case jbvObject: + case jbvArray: + return jperError; + default: - elog(ERROR,"1Wrong state"); + elog(ERROR, "Unknown jsonb value type %d", jb1->type); } return (not ^ eq) ? jperOk : jperNotFound; @@ -1314,6 +1317,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (JsonbType(jb) == jbvObject) { JsonbValue *v, key; + JsonbValue obj; + + if (jb->type == jbvObject) + jb = JsonbWrapInBinary(jb, &obj); key.type = jbvString; key.val.string.val = jspGetString(jsp, &key.val.string.len); @@ -1369,18 +1376,16 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiAnyArray: if (JsonbType(jb) == jbvArray) { - JsonbIterator *it; - int32 r; - JsonbValue v; - hasNext = jspGetNext(jsp, &elem); - it = JsonbIteratorInit(jb->val.binary.data); - while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + if (jb->type == jbvArray) { - if (r == WJB_ELEM) + JsonbValue *el = jb->val.array.elems; + JsonbValue *last_el = el + jb->val.array.nElems; + + for (; el < last_el; el++) { - res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true); + res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true); if (jperIsError(res)) break; @@ -1389,6 +1394,28 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } } + else + { + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken r; + + it = JsonbIteratorInit(jb->val.binary.data); + + while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_ELEM) + { + res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true); + + if (jperIsError(res)) + break; + + if (res == jperOk && !found) + break; + } + } + } } else res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); @@ -1400,6 +1427,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, int innermostArraySize = cxt->innermostArraySize; int i; int size = JsonbArraySize(jb); + bool binary = jb->type == jbvBinary; cxt->innermostArraySize = size; /* for LAST evaluation */ @@ -1448,14 +1476,16 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, for (index = index_from; index <= index_to; index++) { - JsonbValue *v = + JsonbValue *v = binary ? getIthJsonbValueFromContainer(jb->val.binary.data, - (uint32) index); + (uint32) index) : + &jb->val.array.elems[index]; if (v == NULL) continue; - res = recursiveExecuteNext(cxt, jsp, &elem, v, found, false); + res = recursiveExecuteNext(cxt, jsp, &elem, v, found, + !binary); if (jperIsError(res)) break; @@ -1513,6 +1543,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbIterator *it; int32 r; JsonbValue v; + JsonbValue bin; + + if (jb->type == jbvObject) + jb = JsonbWrapInBinary(jb, &bin); hasNext = jspGetNext(jsp, &elem); it = JsonbIteratorInit(jb->val.binary.data); @@ -1565,25 +1599,30 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); break; case jpiAny: - { - bool hasNext = jspGetNext(jsp, &elem); - - /* first try without any intermediate steps */ - if (jsp->content.anybounds.first == 0) { - res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, true); + JsonbValue jbvbuf; - if (res == jperOk && !found) - break; - } + hasNext = jspGetNext(jsp, &elem); - if (jb->type == jbvBinary) - res = recursiveAny(cxt, hasNext ? &elem : NULL, jb, found, - 1, - jsp->content.anybounds.first, - jsp->content.anybounds.last); - break; - } + /* first try without any intermediate steps */ + if (jsp->content.anybounds.first == 0) + { + res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, true); + + if (res == jperOk && !found) + break; + } + + if (jb->type == jbvArray || jb->type == jbvObject) + jb = JsonbWrapInBinary(jb, &jbvbuf); + + if (jb->type == jbvBinary) + res = recursiveAny(cxt, hasNext ? &elem : NULL, jb, found, + 1, + jsp->content.anybounds.first, + jsp->content.anybounds.last); + break; + } case jpiExists: jspGetArg(jsp, &elem); @@ -1859,6 +1898,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, else { int32 r; + JsonbValue bin; JsonbValue key; JsonbValue val; JsonbValue obj; @@ -1869,7 +1909,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, hasNext = jspGetNext(jsp, &elem); - if (!JsonContainerSize(jb->val.binary.data)) + if (jb->type == jbvBinary + ? !JsonContainerSize(jb->val.binary.data) + : !jb->val.object.nPairs) { res = jperNotFound; break; @@ -1886,6 +1928,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, valstr.val.string.val = "value"; valstr.val.string.len = 5; + if (jb->type == jbvObject) + jb = JsonbWrapInBinary(jb, &bin); + it = JsonbIteratorInit(jb->val.binary.data); while ((r = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) @@ -1948,24 +1993,43 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (cxt->lax && JsonbType(jb) == jbvArray) { - JsonbValue v; - JsonbIterator *it; - JsonbIteratorToken tok; JsonPathExecResult res = jperNotFound; - it = JsonbIteratorInit(jb->val.binary.data); - - while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + if (jb->type == jbvArray) { - if (tok == WJB_ELEM) + JsonbValue *elem = jb->val.array.elems; + JsonbValue *last = elem + jb->val.array.nElems; + + for (; elem < last; elem++) { - res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); + res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found); + if (jperIsError(res)) break; if (res == jperOk && !found) break; } } + else + { + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken tok; + + it = JsonbIteratorInit(jb->val.binary.data); + + while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + { + res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); + if (jperIsError(res)) + break; + if (res == jperOk && !found) + break; + } + } + } return res; } @@ -2333,10 +2397,15 @@ wrapItemsInArray(const JsonValueList *items) while ((jbv = JsonValueListNext(items, &it))) { + JsonbValue bin; + if (jbv->type == jbvBinary && JsonContainerIsScalar(jbv->val.binary.data)) JsonbExtractScalar(jbv->val.binary.data, jbv); + if (jbv->type == jbvObject || jbv->type == jbvArray) + jbv = JsonbWrapInBinary(jbv, &bin); + pushJsonbValue(&ps, WJB_ELEM, jbv); } From 0c9b4f8db21ae8ce7ebe9c1041b383c4b753de98 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:04:51 +0300 Subject: [PATCH 46/97] Extract recursiveExecuteUnwrapArray() --- src/backend/utils/adt/jsonpath_exec.c | 69 ++++++++++++++------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 932bc67e81..52d1310591 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -42,7 +42,7 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); -static JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, +static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); static inline JsonbValue *wrapItemsInArray(const JsonValueList *items); @@ -1988,51 +1988,56 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } static JsonPathExecResult -recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found) +recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) { - if (cxt->lax && JsonbType(jb) == jbvArray) + JsonPathExecResult res = jperNotFound; + + if (jb->type == jbvArray) { - JsonPathExecResult res = jperNotFound; + JsonbValue *elem = jb->val.array.elems; + JsonbValue *last = elem + jb->val.array.nElems; - if (jb->type == jbvArray) + for (; elem < last; elem++) { - JsonbValue *elem = jb->val.array.elems; - JsonbValue *last = elem + jb->val.array.nElems; + res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found); - for (; elem < last; elem++) - { - res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found); + if (jperIsError(res)) + break; + if (res == jperOk && !found) + break; + } + } + else + { + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken tok; + + it = JsonbIteratorInit(jb->val.binary.data); + while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (tok == WJB_ELEM) + { + res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); if (jperIsError(res)) break; if (res == jperOk && !found) break; } } - else - { - JsonbValue v; - JsonbIterator *it; - JsonbIteratorToken tok; - - it = JsonbIteratorInit(jb->val.binary.data); + } - while ((tok = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) - { - if (tok == WJB_ELEM) - { - res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); - if (jperIsError(res)) - break; - if (res == jperOk && !found) - break; - } - } - } + return res; +} - return res; - } +static inline JsonPathExecResult +recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) +{ + if (cxt->lax && JsonbType(jb) == jbvArray) + return recursiveExecuteUnwrapArray(cxt, jsp, jb, found); return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); } From 8a3e18f75e744db684fa201fb2fae65cf1b3dee2 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 15:18:35 +0300 Subject: [PATCH 47/97] Add boolean jsonpath expressions --- src/backend/utils/adt/jsonpath.c | 10 ++ src/backend/utils/adt/jsonpath_exec.c | 96 +++++++++++++++++--- src/backend/utils/adt/jsonpath_gram.y | 9 +- src/test/regress/expected/jsonb_jsonpath.out | 43 +++++++++ src/test/regress/expected/jsonpath.out | 22 +++++ src/test/regress/sql/jsonb_jsonpath.sql | 9 ++ src/test/regress/sql/jsonpath.sql | 5 + 7 files changed, 181 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d32322a088..1d51d8b7a5 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -744,6 +744,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiMod || v->type == jpiPlus || v->type == jpiMinus || + v->type == jpiEqual || + v->type == jpiNotEqual || + v->type == jpiGreater || + v->type == jpiGreaterOrEqual || + v->type == jpiLess || + v->type == jpiLessOrEqual || + v->type == jpiAnd || + v->type == jpiOr || + v->type == jpiNot || + v->type == jpiIsUnknown || v->type == jpiType || v->type == jpiSize || v->type == jpiAbs || diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 52d1310591..40338d1b22 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -42,6 +42,9 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); +static inline JsonPathExecResult recursiveExecuteBool(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb); + static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); @@ -1242,6 +1245,34 @@ tryToParseDatetime(const char *template, text *datetime, return ok; } +static inline JsonPathExecResult +appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonValueList *found, JsonPathExecResult res, bool needBool) +{ + JsonPathItem next; + JsonbValue jbv; + bool hasNext = jspGetNext(jsp, &next); + + if (needBool) + { + Assert(!hasNext); + return res; + } + + if (!found && !hasNext) + return jperOk; /* found singleton boolean value */ + + if (jperIsError(res)) + jbv.type = jbvNull; + else + { + jbv.type = jbvBool; + jbv.val.boolean = res == jperOk; + } + + return recursiveExecuteNext(cxt, jsp, &next, &jbv, found, true); +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1254,7 +1285,7 @@ tryToParseDatetime(const char *template, text *datetime, */ static JsonPathExecResult recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found) + JsonbValue *jb, JsonValueList *found, bool needBool) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -1265,7 +1296,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, switch(jsp->type) { case jpiAnd: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, NULL); + res = recursiveExecuteBool(cxt, &elem, jb); if (res != jperNotFound) { JsonPathExecResult res2; @@ -1276,27 +1307,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, */ jspGetRightArg(jsp, &elem); - res2 = recursiveExecute(cxt, &elem, jb, NULL); + res2 = recursiveExecuteBool(cxt, &elem, jb); res = (res2 == jperOk) ? res : res2; } + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiOr: jspGetLeftArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, NULL); + res = recursiveExecuteBool(cxt, &elem, jb); if (res != jperOk) { JsonPathExecResult res2; jspGetRightArg(jsp, &elem); - res2 = recursiveExecute(cxt, &elem, jb, NULL); + res2 = recursiveExecuteBool(cxt, &elem, jb); res = (res2 == jperNotFound) ? res : res2; } + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiNot: jspGetArg(jsp, &elem); - switch ((res = recursiveExecute(cxt, &elem, jb, NULL))) + switch ((res = recursiveExecuteBool(cxt, &elem, jb))) { case jperOk: res = jperNotFound; @@ -1307,11 +1340,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, default: break; } + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiIsUnknown: jspGetArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, NULL); + res = recursiveExecuteBool(cxt, &elem, jb); res = jperIsError(res) ? jperOk : jperNotFound; + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiKey: if (JsonbType(jb) == jbvObject) @@ -1578,6 +1613,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiLessOrEqual: case jpiGreaterOrEqual: res = executeExpr(cxt, jsp, jb); + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiAdd: case jpiSub: @@ -1592,7 +1628,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiFilter: jspGetArg(jsp, &elem); - res = recursiveExecute(cxt, &elem, jb, NULL); + res = recursiveExecuteBool(cxt, &elem, jb); if (res != jperOk) res = jperNotFound; else @@ -1641,6 +1677,8 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!jperIsError(res)) res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; } + + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiNull: case jpiBool: @@ -1976,9 +2014,11 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiStartsWith: res = executeStartsWithPredicate(cxt, jsp, jb); + res = appendBoolResult(cxt, jsp, found, res, needBool); break; case jpiLikeRegex: res = executeLikeRegexPredicate(cxt, jsp, jb); + res = appendBoolResult(cxt, jsp, found, res, needBool); break; default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); @@ -2000,7 +2040,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, for (; elem < last; elem++) { - res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found); + res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false); if (jperIsError(res)) break; @@ -2020,7 +2060,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (tok == WJB_ELEM) { - res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); + res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found, false); if (jperIsError(res)) break; if (res == jperOk && !found) @@ -2039,7 +2079,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (cxt->lax && JsonbType(jb) == jbvArray) return recursiveExecuteUnwrapArray(cxt, jsp, jb, found); - return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false); } /* @@ -2115,7 +2155,39 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } - return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false); +} + +static inline JsonPathExecResult +recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb) +{ + if (jspHasNext(jsp)) + elog(ERROR, "boolean jsonpath item can not have next item"); + + switch (jsp->type) + { + case jpiAnd: + case jpiOr: + case jpiNot: + case jpiIsUnknown: + case jpiEqual: + case jpiNotEqual: + case jpiGreater: + case jpiGreaterOrEqual: + case jpiLess: + case jpiLessOrEqual: + case jpiExists: + case jpiStartsWith: + case jpiLikeRegex: + break; + + default: + elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type); + break; + } + + return recursiveExecuteNoUnwrap(cxt, jsp, jb, NULL, true); } /* diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index fc725e9788..bd95220d84 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -284,6 +284,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type scalar_value path_primary expr pexpr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial opt_datetime_template + expr_or_predicate %type accessor_expr @@ -308,7 +309,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %% result: - mode expr { + mode expr_or_predicate { *result = palloc(sizeof(JsonPathParseResult)); (*result)->expr = $2; (*result)->lax = $1; @@ -316,6 +317,11 @@ result: | /* EMPTY */ { *result = NULL; } ; +expr_or_predicate: + expr { $$ = $1; } + | predicate { $$ = $1; } + ; + mode: STRICT_P { $$ = false; } | LAX_P { $$ = true; } @@ -376,6 +382,7 @@ accessor_expr: path_primary { $$ = list_make1($1); } | '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); } | '(' expr ')' accessor_op { $$ = list_make2($2, $4); } + | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); } | accessor_expr accessor_op { $$ = lappend($1, $2); } ; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 7f9c175761..a81cdff8da 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -801,6 +801,25 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); -- should fail select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); ERROR: Singleton SQL/JSON item required +-- extension: boolean expressions +select _jsonpath_query(jsonb '2', '$ > 1'); + _jsonpath_query +----------------- + true +(1 row) + +select _jsonpath_query(jsonb '2', '$ <= 1'); + _jsonpath_query +----------------- + false +(1 row) + +select _jsonpath_query(jsonb '2', '$ == "2"'); + _jsonpath_query +----------------- + null +(1 row) + select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); _jsonpath_query ----------------- @@ -866,6 +885,30 @@ select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); 4 (1 row) +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)'); + _jsonpath_query +----------------- + true +(1 row) + +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()'); + _jsonpath_query +----------------- + "boolean" +(1 row) + +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()'); + _jsonpath_query +----------------- + "boolean" +(1 row) + +select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()'); + _jsonpath_query +----------------- + "null" +(1 row) + select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); ERROR: SQL/JSON array not found select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index c0509e97be..f58f09764e 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -458,6 +458,22 @@ ERROR: bad jsonpath representation LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; ^ DETAIL: unrecognized flag of LIKE_REGEX predicate at or near """ +select '$ < 1'::jsonpath; + jsonpath +---------- + ($ < 1) +(1 row) + +select '($ < 1) || $.a.b <= $x'::jsonpath; + jsonpath +------------------------------ + ($ < 1 || $."a"."b" <= $"x") +(1 row) + +select '@ + 1'::jsonpath; +ERROR: @ is not allowed in root expressions +LINE 1: select '@ + 1'::jsonpath; + ^ select '($).a.b'::jsonpath; jsonpath ----------- @@ -488,6 +504,12 @@ select '1 + ($.a.b + 2).c.d'::jsonpath; (1 + ($."a"."b" + 2)."c"."d") (1 row) +select '1 + ($.a.b > 2).c.d'::jsonpath; + jsonpath +------------------------------- + (1 + ($."a"."b" > 2)."c"."d") +(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 3abe58a8a1..3d40bb0635 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -163,6 +163,11 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); -- should fail select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); +-- extension: boolean expressions +select _jsonpath_query(jsonb '2', '$ > 1'); +select _jsonpath_query(jsonb '2', '$ <= 1'); +select _jsonpath_query(jsonb '2', '$ == "2"'); + select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); @@ -174,6 +179,10 @@ select _jsonpath_query(jsonb 'null', 'aaa.type()'); select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10'); select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)'); +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()'); +select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()'); +select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()'); select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index f4e7fb8b9b..40eb4eeb45 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -84,11 +84,16 @@ select '$ ? (@ like_regex "pattern" flag "isim")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "xsms")'::jsonpath; select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath; +select '$ < 1'::jsonpath; +select '($ < 1) || $.a.b <= $x'::jsonpath; +select '@ + 1'::jsonpath; + select '($).a.b'::jsonpath; select '($.a.b).c.d'::jsonpath; select '($.a.b + -$.x.y).c.d'::jsonpath; select '(-+$.a.b).c.d'::jsonpath; select '1 + ($.a.b + 2).c.d'::jsonpath; +select '1 + ($.a.b > 2).c.d'::jsonpath; select '$ ? (a < 1)'::jsonpath; select '$ ? (a < -1)'::jsonpath; From 4d70fdb4593d4640b8084e4af8e0f48289b90eb9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 17:48:19 +0300 Subject: [PATCH 48/97] Add _jsonpath_predicate() --- src/backend/utils/adt/jsonpath_exec.c | 46 +++++++++++++++++ src/include/catalog/pg_proc.dat | 7 +++ src/test/regress/expected/jsonb_jsonpath.out | 52 ++++++++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 11 +++++ 4 files changed, 116 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 40338d1b22..bdf31a70ed 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2400,6 +2400,52 @@ jsonb_jsonpath_exists3(PG_FUNCTION_ARGS) return jsonb_jsonpath_exists(fcinfo); } +static inline Datum +jsonb_jsonpath_predicate(FunctionCallInfo fcinfo, List *vars) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonbValue *jbv; + JsonValueList found = { 0 }; + JsonPathExecResult res; + + res = executeJsonPath(jp, vars, jb, &found); + + throwJsonPathError(res); + + if (JsonValueListLength(&found) != 1) + throwJsonPathError(jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED)); + + jbv = JsonValueListHead(&found); + + if (JsonbType(jbv) == jbvScalar) + JsonbExtractScalar(jbv->val.binary.data, jbv); + + PG_FREE_IF_COPY(jb, 0); + PG_FREE_IF_COPY(jp, 1); + + if (jbv->type == jbvNull) + PG_RETURN_NULL(); + + if (jbv->type != jbvBool) + PG_RETURN_NULL(); /* XXX */ + + PG_RETURN_BOOL(jbv->val.boolean); +} + +Datum +jsonb_jsonpath_predicate2(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_predicate(fcinfo, NIL); +} + +Datum +jsonb_jsonpath_predicate3(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_predicate(fcinfo, + makePassingVars(PG_GETARG_JSONB_P(2))); +} + static Datum jsonb_jsonpath_query(FunctionCallInfo fcinfo) { diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 1245f6ea17..daead66710 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9317,6 +9317,13 @@ proname => '_jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_query3' }, +{ oid => '6073', descr => 'jsonpath predicate test', + proname => '_jsonpath_predicate', prorettype => 'bool', + proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_predicate2' }, +{ oid => '6074', descr => 'jsonpath predicate test', + proname => '_jsonpath_predicate', prorettype => 'bool', + proargtypes => 'jsonb jsonpath jsonb', + prosrc => 'jsonb_jsonpath_predicate3' }, # txid { oid => '2939', descr => 'I/O', diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index a81cdff8da..705200d733 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -820,6 +820,58 @@ select _jsonpath_query(jsonb '2', '$ == "2"'); null (1 row) +select _jsonpath_predicate(jsonb '2', '$ > 1'); + _jsonpath_predicate +--------------------- + t +(1 row) + +select _jsonpath_predicate(jsonb '2', '$ <= 1'); + _jsonpath_predicate +--------------------- + f +(1 row) + +select _jsonpath_predicate(jsonb '2', '$ == "2"'); + _jsonpath_predicate +--------------------- + +(1 row) + +select _jsonpath_predicate(jsonb '2', '1'); + _jsonpath_predicate +--------------------- + +(1 row) + +select _jsonpath_predicate(jsonb '{}', '$'); + _jsonpath_predicate +--------------------- + +(1 row) + +select _jsonpath_predicate(jsonb '[]', '$'); + _jsonpath_predicate +--------------------- + +(1 row) + +select _jsonpath_predicate(jsonb '[1,2,3]', '$[*]'); +ERROR: Singleton SQL/JSON item required +select _jsonpath_predicate(jsonb '[]', '$[*]'); +ERROR: Singleton SQL/JSON item required +select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); + _jsonpath_predicate +--------------------- + f +(1 row) + +select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + _jsonpath_predicate +--------------------- + t +(1 row) + select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); _jsonpath_query ----------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 3d40bb0635..e7c1c1d631 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -168,6 +168,17 @@ select _jsonpath_query(jsonb '2', '$ > 1'); select _jsonpath_query(jsonb '2', '$ <= 1'); select _jsonpath_query(jsonb '2', '$ == "2"'); +select _jsonpath_predicate(jsonb '2', '$ > 1'); +select _jsonpath_predicate(jsonb '2', '$ <= 1'); +select _jsonpath_predicate(jsonb '2', '$ == "2"'); +select _jsonpath_predicate(jsonb '2', '1'); +select _jsonpath_predicate(jsonb '{}', '$'); +select _jsonpath_predicate(jsonb '[]', '$'); +select _jsonpath_predicate(jsonb '[1,2,3]', '$[*]'); +select _jsonpath_predicate(jsonb '[]', '$[*]'); +select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); +select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); From 8eb3b44378e0042c461c41a6c567b051ba6a79bc Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 12 May 2017 00:59:23 +0300 Subject: [PATCH 49/97] Add jsonpath operators @*, @?, @~ --- src/include/catalog/pg_operator.dat | 11 + src/include/catalog/pg_proc.dat | 6 +- src/test/regress/expected/jsonb_jsonpath.out | 1338 +++++++++--------- src/test/regress/sql/jsonb_jsonpath.sql | 594 ++++---- 4 files changed, 989 insertions(+), 960 deletions(-) diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index d9b6bad614..9cb464f439 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3336,5 +3336,16 @@ { oid => '3287', descr => 'delete path', oprname => '#-', oprleft => 'jsonb', oprright => '_text', oprresult => 'jsonb', oprcode => 'jsonb_delete_path' }, +{ oid => '6075', descr => 'jsonpath items', + oprname => '@*', oprleft => 'jsonb', oprright => 'jsonpath', + oprresult => 'jsonb', oprcode => '_jsonpath_query(jsonb,jsonpath)' }, +{ oid => '6076', descr => 'jsonpath exists', + oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath', + oprresult => 'bool', oprcode => '_jsonpath_exists(jsonb,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6107', descr => 'jsonpath predicate', + oprname => '@~', oprleft => 'jsonb', oprright => 'jsonpath', + oprresult => 'bool', oprcode => '_jsonpath_predicate(jsonb,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index daead66710..4798f0f908 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9303,10 +9303,10 @@ { oid => '6053', descr => 'I/O', proname => 'jsonpath_out', prorettype => 'cstring', proargtypes => 'jsonpath', prosrc => 'jsonpath_out' }, -{ oid => '6054', descr => 'jsonpath exists test', +{ oid => '6054', descr => 'implementation of @? operator', proname => '_jsonpath_exists', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists2' }, -{ oid => '6055', descr => 'jsonpath query', +{ oid => '6055', descr => 'implementation of @* operator', proname => '_jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_query2' }, @@ -9317,7 +9317,7 @@ proname => '_jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_query3' }, -{ oid => '6073', descr => 'jsonpath predicate test', +{ oid => '6073', descr => 'implementation of @~ operator', proname => '_jsonpath_predicate', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_predicate2' }, { oid => '6074', descr => 'jsonpath predicate test', diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 705200d733..25d5e14bfd 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1,311 +1,311 @@ -select _jsonpath_exists(jsonb '{"a": 12}', '$.a.b'); - _jsonpath_exists ------------------- +select jsonb '{"a": 12}' @? '$.a.b'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"a": 12}', '$.b'); - _jsonpath_exists ------------------- +select jsonb '{"a": 12}' @? '$.b'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.a.a'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"a": 12}}' @? '$.a.a'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"a": 12}}' @? '$.*.a'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); - _jsonpath_exists ------------------- +select jsonb '{"b": {"a": 12}}' @? '$.*.a'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{}', '$.*'); - _jsonpath_exists ------------------- +select jsonb '{}' @? '$.*'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); - _jsonpath_exists ------------------- +select jsonb '{"a": 1}' @? '$.*'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{3}'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[]', '$.[*]'); - _jsonpath_exists ------------------- +select jsonb '[]' @? '$.[*]'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[*]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[*]'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[1]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[1]'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[1]', 'strict $.[1]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? 'strict $.[1]'; + ?column? +---------- (1 row) -select _jsonpath_query(jsonb '[1]', 'strict $[1]'); +select jsonb '[1]' @* 'strict $[1]'; ERROR: Invalid SQL/JSON subscript -select _jsonpath_exists(jsonb '[1]', '$.[0]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[0]'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[0.3]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[0.3]'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[0.5]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[0.5]'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[0.9]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[0.9]'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1]', '$.[1.2]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? '$.[1.2]'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[1]', 'strict $.[1.2]'); - _jsonpath_exists ------------------- +select jsonb '[1]' @? 'strict $.[1.2]'; + ?column? +---------- (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); - _jsonpath_exists ------------------- +select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); - _jsonpath_exists ------------------- +select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); - _jsonpath_exists ------------------- +select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'strict $ ? (@.a[*] >= @.b[*])'); - _jsonpath_exists ------------------- +select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); - _jsonpath_exists ------------------- +select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); - _jsonpath_exists ------------------- +select jsonb '1' @? '$ ? ((@ == "1") is unknown)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); - _jsonpath_exists ------------------- +select jsonb '1' @? '$ ? ((@ == 1) is unknown)'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[{"a": 1}, {"a": 2}]', '$[0 to 1] ? (@.a > 1)'); - _jsonpath_exists ------------------- +select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)'; + ?column? +---------- t (1 row) -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); - _jsonpath_query ------------------ +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a'; + ?column? +---------- 12 (1 row) -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); - _jsonpath_query ------------------ +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b'; + ?column? +----------- {"a": 13} (1 row) -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); - _jsonpath_query ------------------ +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*'; + ?column? +----------- 12 {"a": 13} (2 rows) -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); - _jsonpath_query ------------------ +select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; + ?column? +---------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a'; + ?column? +---------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].*'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*'; + ?column? +---------- 13 14 (2 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[1].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a'; + ?column? +---------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[2].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0,1].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a'; + ?column? +---------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 10].a'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a'; + ?column? +---------- 13 (1 row) -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]', '$.[2.5 - 1 to @.size() - 2]'); - _jsonpath_query ------------------ +select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]'; + ?column? +----------- {"a": 13} {"b": 14} "ccc" (3 rows) -select * from _jsonpath_query(jsonb '1', 'lax $[0]'); - _jsonpath_query ------------------ +select jsonb '1' @* 'lax $[0]'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '1', 'lax $[*]'); - _jsonpath_query ------------------ +select jsonb '1' @* 'lax $[*]'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); - _jsonpath_query ------------------ +select jsonb '[1]' @* 'lax $[0]'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); - _jsonpath_query ------------------ +select jsonb '[1]' @* 'lax $[*]'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); - _jsonpath_query ------------------ +select jsonb '[1,2,3]' @* 'lax $[*]'; + ?column? +---------- 1 2 3 (3 rows) -select * from _jsonpath_query(jsonb '[]', '$[last]'); - _jsonpath_query ------------------ +select jsonb '[]' @* '$[last]'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '[]', 'strict $[last]'); +select jsonb '[]' @* 'strict $[last]'; ERROR: Invalid SQL/JSON subscript -select * from _jsonpath_query(jsonb '[1]', '$[last]'); - _jsonpath_query ------------------ +select jsonb '[1]' @* '$[last]'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]'); - _jsonpath_query ------------------ +select jsonb '[1,2,3]' @* '$[last]'; + ?column? +---------- 3 (1 row) -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]'); - _jsonpath_query ------------------ +select jsonb '[1,2,3]' @* '$[last - 1]'; + ?column? +---------- 2 (1 row) -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]'); - _jsonpath_query ------------------ +select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]'; + ?column? +---------- 3 (1 row) -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]'); +select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]'; ERROR: Invalid SQL/JSON subscript select * from _jsonpath_query(jsonb '{"a": 10}', '$'); _jsonpath_query @@ -380,211 +380,211 @@ select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); null (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**'); - _jsonpath_query +select jsonb '{"a": {"b": 1}}' @* 'lax $.**'; + ?column? ----------------- {"a": {"b": 1}} {"b": 1} 1 (3 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}'; + ?column? +---------- {"b": 1} (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}'; + ?column? +---------- {"b": 1} 1 (2 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2,}'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3,}'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0,}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,2}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; + ?column? +---------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0,}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,2}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? (@ > 0)'); - _jsonpath_query ------------------ +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; + ?column? +---------- 1 (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)'; + ?column? +---------- f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)'; + ?column? +---------- f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; + ?column? +---------- f (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? ( @ > 0)'); - _jsonpath_exists ------------------- +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; + ?column? +---------- t (1 row) -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); - _jsonpath_query ------------------ +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; + ?column? +---------- {"x": 2} (1 row) -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); - _jsonpath_query ------------------ +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); - _jsonpath_query ------------------ +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; + ?column? +---------- {"x": 2} (1 row) @@ -639,226 +639,226 @@ from "null" | "null" | null (9 rows) -select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$ ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$ ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? ($.c.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)'; + ?column? +---------- t (1 row) -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)'); - _jsonpath_query +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)'; + ?column? ------------------ {"a": 2, "b": 1} (1 row) -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))'); - _jsonpath_query +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))'; + ?column? ------------------ {"a": 2, "b": 1} (1 row) -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)'); - _jsonpath_query +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)'; + ?column? ------------------ {"a": 2, "b": 1} (1 row) -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (.b + 1))'); - _jsonpath_query +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))'; + ?column? ------------------ {"a": 2, "b": 1} (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - 1)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -1)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -.b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 - - .b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)'); - _jsonpath_exists ------------------- +select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); - _jsonpath_exists ------------------- +select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); - _jsonpath_exists ------------------- +select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); - _jsonpath_exists ------------------- +select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)'; + ?column? +---------- t (1 row) -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); - _jsonpath_exists ------------------- +select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)'; + ?column? +---------- f (1 row) -select _jsonpath_exists(jsonb '1', '$ ? ($ > 0)'); - _jsonpath_exists ------------------- +select jsonb '1' @? '$ ? ($ > 0)'; + ?column? +---------- t (1 row) -- unwrapping of operator arguments in lax mode -select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); - _jsonpath_query ------------------ +select jsonb '{"a": [2]}' @* 'lax $.a * 3'; + ?column? +---------- 6 (1 row) -select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3'); - _jsonpath_query ------------------ +select jsonb '{"a": [2]}' @* 'lax $.a + 3'; + ?column? +---------- 5 (1 row) -select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); - _jsonpath_query ------------------ +select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a'; + ?column? +---------- -2 -3 -4 (3 rows) -- should fail -select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); +select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3'; ERROR: Singleton SQL/JSON item required -- extension: boolean expressions -select _jsonpath_query(jsonb '2', '$ > 1'); - _jsonpath_query ------------------ +select jsonb '2' @* '$ > 1'; + ?column? +---------- true (1 row) -select _jsonpath_query(jsonb '2', '$ <= 1'); - _jsonpath_query ------------------ +select jsonb '2' @* '$ <= 1'; + ?column? +---------- false (1 row) -select _jsonpath_query(jsonb '2', '$ == "2"'); - _jsonpath_query ------------------ +select jsonb '2' @* '$ == "2"'; + ?column? +---------- null (1 row) -select _jsonpath_predicate(jsonb '2', '$ > 1'); - _jsonpath_predicate ---------------------- +select jsonb '2' @~ '$ > 1'; + ?column? +---------- t (1 row) -select _jsonpath_predicate(jsonb '2', '$ <= 1'); - _jsonpath_predicate ---------------------- +select jsonb '2' @~ '$ <= 1'; + ?column? +---------- f (1 row) -select _jsonpath_predicate(jsonb '2', '$ == "2"'); - _jsonpath_predicate ---------------------- +select jsonb '2' @~ '$ == "2"'; + ?column? +---------- (1 row) -select _jsonpath_predicate(jsonb '2', '1'); - _jsonpath_predicate ---------------------- +select jsonb '2' @~ '1'; + ?column? +---------- (1 row) -select _jsonpath_predicate(jsonb '{}', '$'); - _jsonpath_predicate ---------------------- +select jsonb '{}' @~ '$'; + ?column? +---------- (1 row) -select _jsonpath_predicate(jsonb '[]', '$'); - _jsonpath_predicate ---------------------- +select jsonb '[]' @~ '$'; + ?column? +---------- (1 row) -select _jsonpath_predicate(jsonb '[1,2,3]', '$[*]'); +select jsonb '[1,2,3]' @~ '$[*]'; ERROR: Singleton SQL/JSON item required -select _jsonpath_predicate(jsonb '[]', '$[*]'); +select jsonb '[]' @~ '$[*]'; ERROR: Singleton SQL/JSON item required select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); _jsonpath_predicate @@ -872,21 +872,21 @@ select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] t (1 row) -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); - _jsonpath_query ------------------ +select jsonb '[null,1,true,"a",[],{}]' @* '$.type()'; + ?column? +---------- "array" (1 row) -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); - _jsonpath_query ------------------ +select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()'; + ?column? +---------- "array" (1 row) -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); - _jsonpath_query ------------------ +select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()'; + ?column? +----------- "null" "number" "boolean" @@ -895,77 +895,77 @@ select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); "object" (6 rows) -select _jsonpath_query(jsonb 'null', 'null.type()'); - _jsonpath_query ------------------ +select jsonb 'null' @* 'null.type()'; + ?column? +---------- "null" (1 row) -select _jsonpath_query(jsonb 'null', 'true.type()'); - _jsonpath_query ------------------ +select jsonb 'null' @* 'true.type()'; + ?column? +----------- "boolean" (1 row) -select _jsonpath_query(jsonb 'null', '123.type()'); - _jsonpath_query ------------------ +select jsonb 'null' @* '123.type()'; + ?column? +---------- "number" (1 row) -select _jsonpath_query(jsonb 'null', '"123".type()'); - _jsonpath_query ------------------ +select jsonb 'null' @* '"123".type()'; + ?column? +---------- "string" (1 row) -select _jsonpath_query(jsonb 'null', 'aaa.type()'); - _jsonpath_query ------------------ +select jsonb 'null' @* 'aaa.type()'; + ?column? +---------- "string" (1 row) -select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10'); - _jsonpath_query ------------------ +select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10'; + ?column? +---------- 13 (1 row) -select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); - _jsonpath_query ------------------ +select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10'; + ?column? +---------- 4 (1 row) -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)'); - _jsonpath_query ------------------ +select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)'; + ?column? +---------- true (1 row) -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()'); - _jsonpath_query ------------------ +select jsonb '[1, 2, 3]' @* '($[*] > 3).type()'; + ?column? +----------- "boolean" (1 row) -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()'); - _jsonpath_query ------------------ +select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()'; + ?column? +----------- "boolean" (1 row) -select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()'); - _jsonpath_query ------------------ +select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()'; + ?column? +---------- "null" (1 row) -select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); +select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()'; ERROR: SQL/JSON array not found -select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); - _jsonpath_query ------------------ +select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()'; + ?column? +---------- 1 1 1 @@ -977,9 +977,9 @@ select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}] 1 (9 rows) -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); - _jsonpath_query ------------------ +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()'; + ?column? +---------- 0 1 2 @@ -987,9 +987,9 @@ select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); 5.6 (5 rows) -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); - _jsonpath_query ------------------ +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()'; + ?column? +---------- 0 1 -2 @@ -997,9 +997,9 @@ select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); 5 (5 rows) -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); - _jsonpath_query ------------------ +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()'; + ?column? +---------- 0 1 -2 @@ -1007,9 +1007,9 @@ select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); 6 (5 rows) -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); - _jsonpath_query ------------------ +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()'; + ?column? +---------- 0 1 2 @@ -1017,9 +1017,9 @@ select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); 6 (5 rows) -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); - _jsonpath_query ------------------ +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()'; + ?column? +---------- "number" "number" "number" @@ -1027,443 +1027,441 @@ select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type "number" (5 rows) -select _jsonpath_query(jsonb '[{},1]', '$[*].keyvalue()'); +select jsonb '[{},1]' @* '$[*].keyvalue()'; ERROR: SQL/JSON object not found -select _jsonpath_query(jsonb '{}', '$.keyvalue()'); - _jsonpath_query ------------------ +select jsonb '{}' @* '$.keyvalue()'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); - _jsonpath_query +select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()'; + ?column? ------------------------------------- {"key": "a", "value": 1} {"key": "b", "value": [1, 2]} {"key": "c", "value": {"a": "bbb"}} (3 rows) -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); - _jsonpath_query +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()'; + ?column? ------------------------------------- {"key": "a", "value": 1} {"key": "b", "value": [1, 2]} {"key": "c", "value": {"a": "bbb"}} (3 rows) -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()'; ERROR: SQL/JSON object not found -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); - _jsonpath_query +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()'; + ?column? ------------------------------------- {"key": "a", "value": 1} {"key": "b", "value": [1, 2]} {"key": "c", "value": {"a": "bbb"}} (3 rows) -select _jsonpath_query(jsonb 'null', '$.double()'); +select jsonb 'null' @* '$.double()'; ERROR: Non-numeric SQL/JSON item -select _jsonpath_query(jsonb 'true', '$.double()'); +select jsonb 'true' @* '$.double()'; ERROR: Non-numeric SQL/JSON item -select _jsonpath_query(jsonb '[]', '$.double()'); - _jsonpath_query ------------------ +select jsonb '[]' @* '$.double()'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '[]', 'strict $.double()'); +select jsonb '[]' @* 'strict $.double()'; ERROR: Non-numeric SQL/JSON item -select _jsonpath_query(jsonb '{}', '$.double()'); +select jsonb '{}' @* '$.double()'; ERROR: Non-numeric SQL/JSON item -select _jsonpath_query(jsonb '1.23', '$.double()'); - _jsonpath_query ------------------ +select jsonb '1.23' @* '$.double()'; + ?column? +---------- 1.23 (1 row) -select _jsonpath_query(jsonb '"1.23"', '$.double()'); - _jsonpath_query ------------------ +select jsonb '"1.23"' @* '$.double()'; + ?column? +---------- 1.23 (1 row) -select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); +select jsonb '"1.23aaa"' @* '$.double()'; ERROR: Non-numeric SQL/JSON item -select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); - _jsonpath_query ------------------ +select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")'; + ?column? +---------- "abc" "abcabc" (2 rows) -select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); - _jsonpath_query +select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? ---------------------------- ["", "a", "abc", "abcabc"] (1 row) -select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); - _jsonpath_query ------------------ +select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); - _jsonpath_query ------------------ +select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); - _jsonpath_query +select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)'; + ?column? ---------------------------- ["abc", "abcabc", null, 1] (1 row) -select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); - _jsonpath_query +select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")'; + ?column? ---------------------------- [null, 1, "abc", "abcabc"] (1 row) -select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); - _jsonpath_query +select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)'; + ?column? ---------------------------- [null, 1, "abd", "abdabc"] (1 row) -select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); - _jsonpath_query ------------------ +select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)'; + ?column? +---------- null 1 (2 rows) -select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); - _jsonpath_query ------------------ +select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")'; + ?column? +---------- "abc" "abdacb" (2 rows) -select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'); - _jsonpath_query ------------------ +select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'; + ?column? +---------- "abc" "aBdC" "abdacb" (3 rows) -select _jsonpath_query(jsonb 'null', '$.datetime()'); +select jsonb 'null' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb 'true', '$.datetime()'); +select jsonb 'true' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb '1', '$.datetime()'); +select jsonb '1' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb '[]', '$.datetime()'); - _jsonpath_query ------------------ +select jsonb '[]' @* '$.datetime()'; + ?column? +---------- (0 rows) -select _jsonpath_query(jsonb '[]', 'strict $.datetime()'); +select jsonb '[]' @* 'strict $.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb '{}', '$.datetime()'); +select jsonb '{}' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb '""', '$.datetime()'); +select jsonb '""' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy")'); - _jsonpath_query ------------------ +select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; + ?column? +-------------- "2017-03-10" (1 row) -select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); - _jsonpath_query ------------------ +select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; + ?column? +---------- "date" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); - _jsonpath_query ------------------ +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; + ?column? +-------------- "2017-03-10" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); - _jsonpath_query ------------------ +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()'; + ?column? +---------- "date" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); - _jsonpath_query +select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()'; + ?column? ------------------------------- "timestamp without time zone" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'; + ?column? ---------------------------- "timestamp with time zone" (1 row) -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime("HH24:MI:SS").type()'); - _jsonpath_query +select jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()'; + ?column? -------------------------- "time without time zone" (1 row) -select _jsonpath_query(jsonb '"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); - _jsonpath_query +select jsonb '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()'; + ?column? ----------------------- "time with time zone" (1 row) set time zone '+00'; -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; + ?column? ----------------------- "2017-03-10T12:34:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-10T12:34:00+00:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-10T07:34:00+00:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-10T17:34:00+00:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? ----------------------------- "2017-03-10T07:14:00+00:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? ----------------------------- "2017-03-10T17:54:00+00:00" (1 row) -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); - _jsonpath_query ------------------ +select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; + ?column? +------------ "12:34:00" (1 row) -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00+00:00" (1 row) -select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00+05:00" (1 row) -select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00-05:00" (1 row) -select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? ------------------ "12:34:00+05:20" (1 row) -select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? ------------------ "12:34:00-05:20" (1 row) set time zone '+10'; -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; + ?column? ----------------------- "2017-03-10T12:34:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-10T12:34:00+10:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-10T17:34:00+10:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? ----------------------------- "2017-03-11T03:34:00+10:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? ----------------------------- "2017-03-10T17:14:00+10:00" (1 row) -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? ----------------------------- "2017-03-11T03:54:00+10:00" (1 row) -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); - _jsonpath_query ------------------ +select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; + ?column? +------------ "12:34:00" (1 row) -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00+10:00" (1 row) -select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00+05:00" (1 row) -select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); - _jsonpath_query +select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; + ?column? ------------------ "12:34:00-05:00" (1 row) -select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? ------------------ "12:34:00+05:20" (1 row) -select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); - _jsonpath_query +select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? ------------------ "12:34:00-05:20" (1 row) set time zone default; -select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime().type()'); - _jsonpath_query ------------------ +select jsonb '"2017-03-10"' @* '$.datetime().type()'; + ?column? +---------- "date" (1 row) -select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime()'); - _jsonpath_query ------------------ +select jsonb '"2017-03-10"' @* '$.datetime()'; + ?column? +-------------- "2017-03-10" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()'; + ?column? ------------------------------- "timestamp without time zone" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()'; + ?column? ----------------------- "2017-03-10T12:34:56" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; + ?column? ---------------------------- "timestamp with time zone" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; + ?column? ----------------------------- "2017-03-10T01:34:56-08:00" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; + ?column? ---------------------------- "timestamp with time zone" (1 row) -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime()'); - _jsonpath_query +select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; + ?column? ----------------------------- "2017-03-10T01:24:56-08:00" (1 row) -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"12:34:56"' @* '$.datetime().type()'; + ?column? -------------------------- "time without time zone" (1 row) -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime()'); - _jsonpath_query ------------------ +select jsonb '"12:34:56"' @* '$.datetime()'; + ?column? +------------ "12:34:56" (1 row) -select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"12:34:56 +3"' @* '$.datetime().type()'; + ?column? ----------------------- "time with time zone" (1 row) -select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime()'); - _jsonpath_query +select jsonb '"12:34:56 +3"' @* '$.datetime()'; + ?column? ------------------ "12:34:56+03:00" (1 row) -select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime().type()'); - _jsonpath_query +select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()'; + ?column? ----------------------- "time with time zone" (1 row) -select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime()'); - _jsonpath_query +select jsonb '"12:34:56 +3:10"' @* '$.datetime()'; + ?column? ------------------ "12:34:56+03:10" (1 row) set time zone '+00'; -- date comparison -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10" "2017-03-10T00:00:00" "2017-03-10T00:00:00+00:00" (3 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10" "2017-03-11" @@ -1472,43 +1470,39 @@ select _jsonpath_query(jsonb "2017-03-10T00:00:00+00:00" (5 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-09" "2017-03-09T21:02:03+00:00" (2 rows) -- time comparison -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:35:00" "12:35:00+00:00" (2 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:35:00" "12:36:00" "12:35:00+00:00" (3 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:34:00" "12:35:00+01:00" @@ -1516,20 +1510,18 @@ select _jsonpath_query(jsonb (3 rows) -- timetz comparison -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:35:00+01:00" (1 row) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:35:00+01:00" "12:36:00+01:00" @@ -1538,11 +1530,10 @@ select _jsonpath_query(jsonb "12:35:00" (5 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ------------------ "12:34:00+01:00" "12:35:00+02:00" @@ -1550,21 +1541,19 @@ select _jsonpath_query(jsonb (3 rows) -- timestamp comparison -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T12:35:00" "2017-03-10T12:35:00+00:00" (2 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T12:35:00" "2017-03-10T12:36:00" @@ -1573,11 +1562,10 @@ select _jsonpath_query(jsonb "2017-03-11" (5 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T12:34:00" "2017-03-10T11:35:00+00:00" @@ -1585,21 +1573,19 @@ select _jsonpath_query(jsonb (3 rows) -- timestamptz comparison -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T11:35:00+00:00" "2017-03-10T11:35:00" (2 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T11:35:00+00:00" "2017-03-10T11:36:00+00:00" @@ -1609,11 +1595,10 @@ select _jsonpath_query(jsonb "2017-03-11" (6 rows) -select _jsonpath_query(jsonb - '["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"))' -); - _jsonpath_query +select jsonb + '["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"))'; + ?column? ----------------------------- "2017-03-10T11:34:00+00:00" "2017-03-10T10:35:00+00:00" @@ -1622,3 +1607,40 @@ select _jsonpath_query(jsonb (4 rows) set time zone default; +-- jsonpath operators +SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]'; + ?column? +---------- + {"a": 1} + {"a": 2} +(2 rows) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; + ?column? +---------- +(0 rows) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; + ?column? +---------- + f +(1 row) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1'; + ?column? +---------- + t +(1 row) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; + ?column? +---------- + f +(1 row) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index e7c1c1d631..0281b79e09 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -1,57 +1,57 @@ -select _jsonpath_exists(jsonb '{"a": 12}', '$.a.b'); -select _jsonpath_exists(jsonb '{"a": 12}', '$.b'); -select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.a.a'); -select _jsonpath_exists(jsonb '{"a": {"a": 12}}', '$.*.a'); -select _jsonpath_exists(jsonb '{"b": {"a": 12}}', '$.*.a'); -select _jsonpath_exists(jsonb '{}', '$.*'); -select _jsonpath_exists(jsonb '{"a": 1}', '$.*'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); -select _jsonpath_exists(jsonb '{"a": {"b": 1}}', 'lax $.**{3}'); -select _jsonpath_exists(jsonb '[]', '$.[*]'); -select _jsonpath_exists(jsonb '[1]', '$.[*]'); -select _jsonpath_exists(jsonb '[1]', '$.[1]'); -select _jsonpath_exists(jsonb '[1]', 'strict $.[1]'); -select _jsonpath_query(jsonb '[1]', 'strict $[1]'); -select _jsonpath_exists(jsonb '[1]', '$.[0]'); -select _jsonpath_exists(jsonb '[1]', '$.[0.3]'); -select _jsonpath_exists(jsonb '[1]', '$.[0.5]'); -select _jsonpath_exists(jsonb '[1]', '$.[0.9]'); -select _jsonpath_exists(jsonb '[1]', '$.[1.2]'); -select _jsonpath_exists(jsonb '[1]', 'strict $.[1.2]'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] > @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,5]}', '$ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', '$ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,"5"]}', 'strict $ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '{"a": [1,2,3], "b": [3,4,null]}', '$ ? (@.a[*] >= @.b[*])'); -select _jsonpath_exists(jsonb '1', '$ ? ((@ == "1") is unknown)'); -select _jsonpath_exists(jsonb '1', '$ ? ((@ == 1) is unknown)'); -select _jsonpath_exists(jsonb '[{"a": 1}, {"a": 2}]', '$[0 to 1] ? (@.a > 1)'); - -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a'); -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b'); -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*'); -select * from _jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[*].*'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[1].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[2].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0,1].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $.[0 to 10].a'); -select * from _jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]', '$.[2.5 - 1 to @.size() - 2]'); -select * from _jsonpath_query(jsonb '1', 'lax $[0]'); -select * from _jsonpath_query(jsonb '1', 'lax $[*]'); -select * from _jsonpath_query(jsonb '[1]', 'lax $[0]'); -select * from _jsonpath_query(jsonb '[1]', 'lax $[*]'); -select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]'); -select * from _jsonpath_query(jsonb '[]', '$[last]'); -select * from _jsonpath_query(jsonb '[]', 'strict $[last]'); -select * from _jsonpath_query(jsonb '[1]', '$[last]'); -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]'); -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]'); -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]'); -select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]'); +select jsonb '{"a": 12}' @? '$.a.b'; +select jsonb '{"a": 12}' @? '$.b'; +select jsonb '{"a": {"a": 12}}' @? '$.a.a'; +select jsonb '{"a": {"a": 12}}' @? '$.*.a'; +select jsonb '{"b": {"a": 12}}' @? '$.*.a'; +select jsonb '{}' @? '$.*'; +select jsonb '{"a": 1}' @? '$.*'; +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}'; +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}'; +select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}'; +select jsonb '[]' @? '$.[*]'; +select jsonb '[1]' @? '$.[*]'; +select jsonb '[1]' @? '$.[1]'; +select jsonb '[1]' @? 'strict $.[1]'; +select jsonb '[1]' @* 'strict $[1]'; +select jsonb '[1]' @? '$.[0]'; +select jsonb '[1]' @? '$.[0.3]'; +select jsonb '[1]' @? '$.[0.5]'; +select jsonb '[1]' @? '$.[0.9]'; +select jsonb '[1]' @? '$.[1.2]'; +select jsonb '[1]' @? 'strict $.[1.2]'; +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[*])'; +select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])'; +select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])'; +select jsonb '1' @? '$ ? ((@ == "1") is unknown)'; +select jsonb '1' @? '$ ? ((@ == 1) is unknown)'; +select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)'; + +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a'; +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b'; +select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*'; +select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a'; +select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]'; +select jsonb '1' @* 'lax $[0]'; +select jsonb '1' @* 'lax $[*]'; +select jsonb '[1]' @* 'lax $[0]'; +select jsonb '[1]' @* 'lax $[*]'; +select jsonb '[1,2,3]' @* 'lax $[*]'; +select jsonb '[]' @* '$[last]'; +select jsonb '[]' @* 'strict $[last]'; +select jsonb '[1]' @* '$[last]'; +select jsonb '[1,2,3]' @* '$[last]'; +select jsonb '[1,2,3]' @* '$[last - 1]'; +select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]'; +select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]'; select * from _jsonpath_query(jsonb '{"a": 10}', '$'); select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); @@ -66,43 +66,43 @@ select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value) select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3,}'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1,2}.b ? (@ > 0)'); -select * from _jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2,3}.b ? (@ > 0)'); - -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{0,}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"b": 1}}', '$.**{1,2}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? ( @ > 0)'); -select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? ( @ > 0)'); - -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))'); -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))'); -select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))'); +select jsonb '{"a": {"b": 1}}' @* 'lax $.**'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,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)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; + +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; +select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; --test ternary logic select @@ -131,242 +131,238 @@ from (values (jsonb 'true'), ('false'), ('"null"')) x(x), (values (jsonb 'true'), ('false'), ('"null"')) y(y); -select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$ ? (.a == .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$ ? (.a == .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? (.a == .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.c ? ($.c.a == .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.* ? (.a == .b)'); -select _jsonpath_exists(jsonb '{"a": 1, "b":1}', '$.** ? (.a == .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 1, "b":1}}', '$.** ? (.a == .b)'); - -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)'); -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))'); -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)'); -select _jsonpath_query(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == (.b + 1))'); -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - 1)'); -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -1)'); -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == -.b)'); -select _jsonpath_exists(jsonb '{"c": {"a": -1, "b":1}}', '$.** ? (.a == - .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 - - .b)'); -select _jsonpath_exists(jsonb '{"c": {"a": 0, "b":1}}', '$.** ? (.a == 1 - +.b)'); -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +2)'); -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (+@[*] > +3)'); -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -2)'); -select _jsonpath_exists(jsonb '[1,2,3]', '$ ? (-@[*] < -3)'); -select _jsonpath_exists(jsonb '1', '$ ? ($ > 0)'); +select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)'; +select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)'; +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)'; +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)'; +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)'; +select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)'; +select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)'; + +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)'; +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))'; +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)'; +select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))'; +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)'; +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)'; +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)'; +select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)'; +select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)'; +select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)'; +select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)'; +select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)'; +select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)'; +select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)'; +select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)'; +select jsonb '1' @? '$ ? ($ > 0)'; -- unwrapping of operator arguments in lax mode -select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a * 3'); -select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3'); -select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a'); +select jsonb '{"a": [2]}' @* 'lax $.a * 3'; +select jsonb '{"a": [2]}' @* 'lax $.a + 3'; +select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a'; -- should fail -select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3'); +select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3'; -- extension: boolean expressions -select _jsonpath_query(jsonb '2', '$ > 1'); -select _jsonpath_query(jsonb '2', '$ <= 1'); -select _jsonpath_query(jsonb '2', '$ == "2"'); - -select _jsonpath_predicate(jsonb '2', '$ > 1'); -select _jsonpath_predicate(jsonb '2', '$ <= 1'); -select _jsonpath_predicate(jsonb '2', '$ == "2"'); -select _jsonpath_predicate(jsonb '2', '1'); -select _jsonpath_predicate(jsonb '{}', '$'); -select _jsonpath_predicate(jsonb '[]', '$'); -select _jsonpath_predicate(jsonb '[1,2,3]', '$[*]'); -select _jsonpath_predicate(jsonb '[]', '$[*]'); +select jsonb '2' @* '$ > 1'; +select jsonb '2' @* '$ <= 1'; +select jsonb '2' @* '$ == "2"'; + +select jsonb '2' @~ '$ > 1'; +select jsonb '2' @~ '$ <= 1'; +select jsonb '2' @~ '$ == "2"'; +select jsonb '2' @~ '1'; +select jsonb '{}' @~ '$'; +select jsonb '[]' @~ '$'; +select jsonb '[1,2,3]' @~ '$[*]'; +select jsonb '[]' @~ '$[*]'; select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()'); -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()'); -select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()'); -select _jsonpath_query(jsonb 'null', 'null.type()'); -select _jsonpath_query(jsonb 'null', 'true.type()'); -select _jsonpath_query(jsonb 'null', '123.type()'); -select _jsonpath_query(jsonb 'null', '"123".type()'); -select _jsonpath_query(jsonb 'null', 'aaa.type()'); - -select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10'); -select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10'); -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)'); -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()'); -select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()'); -select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()'); - -select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()'); -select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()'); - -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].abs()'); -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].floor()'); -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()'); -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()'); -select _jsonpath_query(jsonb '[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()'); - -select _jsonpath_query(jsonb '[{},1]', '$[*].keyvalue()'); -select _jsonpath_query(jsonb '{}', '$.keyvalue()'); -select _jsonpath_query(jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()'); -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()'); -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()'); -select _jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()'); - -select _jsonpath_query(jsonb 'null', '$.double()'); -select _jsonpath_query(jsonb 'true', '$.double()'); -select _jsonpath_query(jsonb '[]', '$.double()'); -select _jsonpath_query(jsonb '[]', 'strict $.double()'); -select _jsonpath_query(jsonb '{}', '$.double()'); -select _jsonpath_query(jsonb '1.23', '$.double()'); -select _jsonpath_query(jsonb '"1.23"', '$.double()'); -select _jsonpath_query(jsonb '"1.23aaa"', '$.double()'); - -select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")'); -select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")'); -select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")'); -select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")'); -select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)'); -select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")'); -select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)'); -select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)'); - -select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")'); -select _jsonpath_query(jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'); - -select _jsonpath_query(jsonb 'null', '$.datetime()'); -select _jsonpath_query(jsonb 'true', '$.datetime()'); -select _jsonpath_query(jsonb '1', '$.datetime()'); -select _jsonpath_query(jsonb '[]', '$.datetime()'); -select _jsonpath_query(jsonb '[]', 'strict $.datetime()'); -select _jsonpath_query(jsonb '{}', '$.datetime()'); -select _jsonpath_query(jsonb '""', '$.datetime()'); - -select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy")'); -select _jsonpath_query(jsonb '"10-03-2017"', '$.datetime("dd-mm-yyyy").type()'); -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()'); - -select _jsonpath_query(jsonb '"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'); -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime("HH24:MI:SS").type()'); -select _jsonpath_query(jsonb '"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()'); +select jsonb '[null,1,true,"a",[],{}]' @* '$.type()'; +select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()'; +select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()'; +select jsonb 'null' @* 'null.type()'; +select jsonb 'null' @* 'true.type()'; +select jsonb 'null' @* '123.type()'; +select jsonb 'null' @* '"123".type()'; +select jsonb 'null' @* 'aaa.type()'; + +select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10'; +select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10'; +select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)'; +select jsonb '[1, 2, 3]' @* '($[*] > 3).type()'; +select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()'; +select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()'; + +select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()'; +select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()'; + +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()'; +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()'; +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()'; +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()'; +select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()'; + +select jsonb '[{},1]' @* '$[*].keyvalue()'; +select jsonb '{}' @* '$.keyvalue()'; +select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()'; +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()'; +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()'; +select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()'; + +select jsonb 'null' @* '$.double()'; +select jsonb 'true' @* '$.double()'; +select jsonb '[]' @* '$.double()'; +select jsonb '[]' @* 'strict $.double()'; +select jsonb '{}' @* '$.double()'; +select jsonb '1.23' @* '$.double()'; +select jsonb '"1.23"' @* '$.double()'; +select jsonb '"1.23aaa"' @* '$.double()'; + +select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")'; +select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")'; +select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")'; +select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")'; +select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)'; +select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")'; +select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)'; +select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)'; + +select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")'; +select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'; + +select jsonb 'null' @* '$.datetime()'; +select jsonb 'true' @* '$.datetime()'; +select jsonb '1' @* '$.datetime()'; +select jsonb '[]' @* '$.datetime()'; +select jsonb '[]' @* 'strict $.datetime()'; +select jsonb '{}' @* '$.datetime()'; +select jsonb '""' @* '$.datetime()'; + +select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; +select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()'; + +select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()'; +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'; +select jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()'; +select jsonb '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()'; set time zone '+00'; -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; +select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; set time zone '+10'; -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI")'); -select _jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 +05"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 -05"', '$.datetime("HH24:MI TZH")'); -select _jsonpath_query(jsonb '"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")'); -select _jsonpath_query(jsonb '"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")'); +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; +select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; set time zone default; -select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"2017-03-10"', '$.datetime()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56"', '$.datetime()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3"', '$.datetime()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"2017-03-10 12:34:56 +3:10"', '$.datetime()'); -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"12:34:56"', '$.datetime()'); -select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"12:34:56 +3"', '$.datetime()'); -select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime().type()'); -select _jsonpath_query(jsonb '"12:34:56 +3:10"', '$.datetime()'); +select jsonb '"2017-03-10"' @* '$.datetime().type()'; +select jsonb '"2017-03-10"' @* '$.datetime()'; +select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()'; +select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()'; +select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; +select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; +select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; +select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; +select jsonb '"12:34:56"' @* '$.datetime().type()'; +select jsonb '"12:34:56"' @* '$.datetime()'; +select jsonb '"12:34:56 +3"' @* '$.datetime().type()'; +select jsonb '"12:34:56 +3"' @* '$.datetime()'; +select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()'; +select jsonb '"12:34:56 +3:10"' @* '$.datetime()'; set time zone '+00'; -- date comparison -select _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 + '["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 + '["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 + '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 + '["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 + '["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 + '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 + '["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 + '["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 + '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 + '["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 + '["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 + '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 _jsonpath_query(jsonb - '["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 + '["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 + '["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 + '["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 '[{"a": 1}, {"a": 2}]' @* '$[*]'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; From 2deb60016479a78ba5f4a04af18af49e6f921f78 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 2 Oct 2017 17:51:01 +0300 Subject: [PATCH 50/97] Remove _ prefix in jsonpath function names --- src/include/catalog/pg_operator.dat | 6 +- src/include/catalog/pg_proc.dat | 12 +-- src/test/regress/expected/jsonb_jsonpath.out | 84 ++++++++++---------- src/test/regress/sql/jsonb_jsonpath.sql | 32 ++++---- 4 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 9cb464f439..c997872636 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3338,14 +3338,14 @@ oprresult => 'jsonb', oprcode => 'jsonb_delete_path' }, { oid => '6075', descr => 'jsonpath items', oprname => '@*', oprleft => 'jsonb', oprright => 'jsonpath', - oprresult => 'jsonb', oprcode => '_jsonpath_query(jsonb,jsonpath)' }, + oprresult => 'jsonb', oprcode => 'jsonpath_query(jsonb,jsonpath)' }, { oid => '6076', descr => 'jsonpath exists', oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath', - oprresult => 'bool', oprcode => '_jsonpath_exists(jsonb,jsonpath)', + oprresult => 'bool', oprcode => 'jsonpath_exists(jsonb,jsonpath)', oprrest => 'contsel', oprjoin => 'contjoinsel' }, { oid => '6107', descr => 'jsonpath predicate', oprname => '@~', oprleft => 'jsonb', oprright => 'jsonpath', - oprresult => 'bool', oprcode => '_jsonpath_predicate(jsonb,jsonpath)', + oprresult => 'bool', oprcode => 'jsonpath_predicate(jsonb,jsonpath)', oprrest => 'contsel', oprjoin => 'contjoinsel' }, ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4798f0f908..e84cfc6029 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9304,24 +9304,24 @@ proname => 'jsonpath_out', prorettype => 'cstring', proargtypes => 'jsonpath', prosrc => 'jsonpath_out' }, { oid => '6054', descr => 'implementation of @? operator', - proname => '_jsonpath_exists', prorettype => 'bool', + proname => 'jsonpath_exists', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists2' }, { oid => '6055', descr => 'implementation of @* operator', - proname => '_jsonpath_query', prorows => '1000', proretset => 't', + proname => 'jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_query2' }, { oid => '6056', descr => 'jsonpath exists test', - proname => '_jsonpath_exists', prorettype => 'bool', + proname => 'jsonpath_exists', prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_exists3' }, { oid => '6057', descr => 'jsonpath query', - proname => '_jsonpath_query', prorows => '1000', proretset => 't', + proname => 'jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_query3' }, { oid => '6073', descr => 'implementation of @~ operator', - proname => '_jsonpath_predicate', prorettype => 'bool', + proname => 'jsonpath_predicate', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_predicate2' }, { oid => '6074', descr => 'jsonpath predicate test', - proname => '_jsonpath_predicate', prorettype => 'bool', + proname => 'jsonpath_predicate', prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_predicate3' }, diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 25d5e14bfd..983f783f32 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -307,76 +307,76 @@ select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]'; select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]'; ERROR: Invalid SQL/JSON subscript -select * from _jsonpath_query(jsonb '{"a": 10}', '$'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '{"a": 10}', '$'); + jsonpath_query +---------------- {"a": 10} (1 row) -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); ERROR: could not find 'value' passed variable -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); + jsonpath_query +---------------- {"a": 10} (1 row) -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); + jsonpath_query +---------------- (0 rows) -select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- 10 (1 row) -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- 10 11 12 (3 rows) -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- 10 11 (2 rows) -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); + jsonpath_query +---------------- 10 11 12 (3 rows) -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); + jsonpath_query +---------------- "1" (1 row) -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); + jsonpath_query +---------------- "1" (1 row) -select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); + jsonpath_query +---------------- 1 "2" (2 rows) -select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); - _jsonpath_query ------------------ +select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); + jsonpath_query +---------------- null (1 row) @@ -591,7 +591,7 @@ select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; --test ternary logic select x, y, - _jsonpath_query( + jsonpath_query( jsonb '[true, false, null]', '$[*] ? (@ == true && ($x == true && $y == true) || @ == false && !($x == true && $y == true) || @@ -616,7 +616,7 @@ from select x, y, - _jsonpath_query( + jsonpath_query( jsonb '[true, false, null]', '$[*] ? (@ == true && ($x == true || $y == true) || @ == false && !($x == true || $y == true) || @@ -860,15 +860,15 @@ select jsonb '[1,2,3]' @~ '$[*]'; ERROR: Singleton SQL/JSON item required select jsonb '[]' @~ '$[*]'; ERROR: Singleton SQL/JSON item required -select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); - _jsonpath_predicate ---------------------- +select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); + jsonpath_predicate +-------------------- f (1 row) -select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); - _jsonpath_predicate ---------------------- +select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + jsonpath_predicate +-------------------- t (1 row) diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 0281b79e09..b403028fbe 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -53,18 +53,18 @@ select jsonb '[1,2,3]' @* '$[last - 1]'; select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]'; select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]'; -select * from _jsonpath_query(jsonb '{"a": 10}', '$'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); -select * from _jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); -select * from _jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); -select * from _jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); -select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); -select * from _jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); +select * from jsonpath_query(jsonb '{"a": 10}', '$'); +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); +select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); +select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); +select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); select jsonb '{"a": {"b": 1}}' @* 'lax $.**'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}'; @@ -107,7 +107,7 @@ select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; --test ternary logic select x, y, - _jsonpath_query( + jsonpath_query( jsonb '[true, false, null]', '$[*] ? (@ == true && ($x == true && $y == true) || @ == false && !($x == true && $y == true) || @@ -120,7 +120,7 @@ from select x, y, - _jsonpath_query( + jsonpath_query( jsonb '[true, false, null]', '$[*] ? (@ == true && ($x == true || $y == true) || @ == false && !($x == true || $y == true) || @@ -176,8 +176,8 @@ select jsonb '{}' @~ '$'; select jsonb '[]' @~ '$'; select jsonb '[1,2,3]' @~ '$[*]'; select jsonb '[]' @~ '$[*]'; -select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); -select _jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); +select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); +select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); select jsonb '[null,1,true,"a",[],{}]' @* '$.type()'; select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()'; From eb68ded13eb3e93c30b0544802a1266aec266d60 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 24 Jan 2018 01:39:44 +0300 Subject: [PATCH 51/97] Add jsonpath operator @# for returning conditionally wrapped in array sequences --- src/backend/utils/adt/jsonpath_exec.c | 38 ++++++++++++++++++++ src/include/catalog/pg_operator.dat | 3 ++ src/include/catalog/pg_proc.dat | 7 ++++ src/test/regress/expected/jsonb_jsonpath.out | 18 ++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 4 +++ 5 files changed, 70 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index bdf31a70ed..c05a6ffee3 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2508,6 +2508,44 @@ jsonb_jsonpath_query3(PG_FUNCTION_ARGS) return jsonb_jsonpath_query(fcinfo); } +static Datum +jsonb_jsonpath_query_wrapped(FunctionCallInfo fcinfo, List *vars) +{ + Jsonb *jb = PG_GETARG_JSONB_P(0); + JsonPath *jp = PG_GETARG_JSONPATH_P(1); + JsonValueList found = { 0 }; + JsonPathExecResult res; + int size; + + res = executeJsonPath(jp, vars, jb, &found); + + if (jperIsError(res)) + throwJsonPathError(res); + + size = JsonValueListLength(&found); + + if (size == 0) + PG_RETURN_NULL(); + + if (size == 1) + PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found))); + + PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found))); +} + +Datum +jsonb_jsonpath_query_wrapped2(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_query_wrapped(fcinfo, NIL); +} + +Datum +jsonb_jsonpath_query_wrapped3(PG_FUNCTION_ARGS) +{ + return jsonb_jsonpath_query_wrapped(fcinfo, + makePassingVars(PG_GETARG_JSONB_P(2))); +} + /* Construct a JSON array from the item list */ static inline JsonbValue * wrapItemsInArray(const JsonValueList *items) diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index c997872636..3296233503 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3347,5 +3347,8 @@ oprname => '@~', oprleft => 'jsonb', oprright => 'jsonpath', oprresult => 'bool', oprcode => 'jsonpath_predicate(jsonb,jsonpath)', oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6122', descr => 'jsonpath items wrapped', + oprname => '@#', oprleft => 'jsonb', oprright => 'jsonpath', + oprresult => 'jsonb', oprcode => 'jsonpath_query_wrapped(jsonb,jsonpath)' }, ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index e84cfc6029..ec7e0e538c 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9310,6 +9310,9 @@ proname => 'jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_query2' }, +{ oid => '6124', descr => 'implementation of @# operator', + proname => 'jsonpath_query_wrapped', prorettype => 'jsonb', + proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_query_wrapped2' }, { oid => '6056', descr => 'jsonpath exists test', proname => 'jsonpath_exists', prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_exists3' }, @@ -9317,6 +9320,10 @@ proname => 'jsonpath_query', prorows => '1000', proretset => 't', prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_query3' }, +{ oid => '6125', descr => 'jsonpath query with conditional wrapper', + proname => 'jsonpath_query_wrapped', prorettype => 'jsonb', + proargtypes => 'jsonb jsonpath jsonb', + prosrc => 'jsonb_jsonpath_query_wrapped3' }, { oid => '6073', descr => 'implementation of @~ operator', proname => 'jsonpath_predicate', prorettype => 'bool', proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_predicate2' }, diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 983f783f32..0b9d066851 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1620,6 +1620,24 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; ---------- (0 rows) +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a'; + ?column? +---------- + [1, 2] +(1 row) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; + ?column? +---------- + 1 +(1 row) + +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; + ?column? +---------- + +(1 row) + SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; ?column? ---------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index b403028fbe..a5f875ce73 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -361,6 +361,10 @@ set time zone default; SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; + SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; From e05252653f9ae205f76608aac6df9e3f662ec299 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Nov 2017 03:20:15 +0300 Subject: [PATCH 52/97] Fix jsonpath unquoted identifiers --- src/backend/utils/adt/jsonpath_gram.y | 5 +- src/backend/utils/adt/jsonpath_scan.l | 2 +- src/test/regress/expected/jsonb_jsonpath.out | 6 - src/test/regress/expected/jsonpath.out | 432 +++++++++---------- src/test/regress/sql/jsonb_jsonpath.sql | 1 - src/test/regress/sql/jsonpath.sql | 116 +++-- 6 files changed, 271 insertions(+), 291 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index bd95220d84..11b1fcf7e5 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -272,7 +272,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) } %token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P -%token STRING_P NUMERIC_P INT_P VARIABLE_P +%token IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_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 @@ -449,7 +449,8 @@ key: ; key_name: - STRING_P + IDENT_P + | STRING_P | TO_P | NULL_P | TRUE_P diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 7389d0e7f2..aad4aa2635 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -296,7 +296,7 @@ static keyword keywords[] = { static int checkSpecialVal() { - int res = STRING_P; + int res = IDENT_P; int diff; keyword *StopLow = keywords, *StopHigh = keywords + lengthof(keywords), diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 0b9d066851..10d2c59d3a 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -919,12 +919,6 @@ select jsonb 'null' @* '"123".type()'; "string" (1 row) -select jsonb 'null' @* 'aaa.type()'; - ?column? ----------- - "string" -(1 row) - select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10'; ?column? ---------- diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index f58f09764e..5731263f7c 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -189,12 +189,6 @@ select '$.g ? (@ == 1)'::jsonpath; $."g"?(@ == 1) (1 row) -select '$.g ? (a == 1)'::jsonpath; - jsonpath ------------------- - $."g"?("a" == 1) -(1 row) - select '$.g ? (.a == 1)'::jsonpath; jsonpath -------------------- @@ -207,34 +201,34 @@ select '$.g ? (@.a == 1)'::jsonpath; $."g"?(@."a" == 1) (1 row) -select '$.g ? (@.a == 1 || a == 4)'::jsonpath; - jsonpath --------------------------------- - $."g"?(@."a" == 1 || "a" == 4) +select '$.g ? (@.a == 1 || @.a == 4)'::jsonpath; + jsonpath +---------------------------------- + $."g"?(@."a" == 1 || @."a" == 4) (1 row) -select '$.g ? (@.a == 1 && a == 4)'::jsonpath; - jsonpath --------------------------------- - $."g"?(@."a" == 1 && "a" == 4) +select '$.g ? (@.a == 1 && @.a == 4)'::jsonpath; + jsonpath +---------------------------------- + $."g"?(@."a" == 1 && @."a" == 4) (1 row) -select '$.g ? (@.a == 1 || a == 4 && b == 7)'::jsonpath; - jsonpath --------------------------------------------- - $."g"?(@."a" == 1 || "a" == 4 && "b" == 7) +select '$.g ? (@.a == 1 || @.a == 4 && @.b == 7)'::jsonpath; + jsonpath +------------------------------------------------ + $."g"?(@."a" == 1 || @."a" == 4 && @."b" == 7) (1 row) -select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; - jsonpath ------------------------------------------------ - $."g"?(@."a" == 1 || !("a" == 4) && "b" == 7) +select '$.g ? (@.a == 1 || !(@.a == 4) && @.b == 7)'::jsonpath; + jsonpath +--------------------------------------------------- + $."g"?(@."a" == 1 || !(@."a" == 4) && @."b" == 7) (1 row) -select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; - jsonpath -------------------------------------------------------------- - $."g"?(@."a" == 1 || !("x" >= 123 || "a" == 4) && "b" == 7) +select '$.g ? (@.a == 1 || !(@.x >= 123 || @.a == 4) && @.b == 7)'::jsonpath; + jsonpath +------------------------------------------------------------------- + $."g"?(@."a" == 1 || !(@."x" >= 123 || @."a" == 4) && @."b" == 7) (1 row) select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; @@ -243,10 +237,10 @@ select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; $."g"?(@."x" >= @[*]?(@."a" > "abc")) (1 row) -select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; - jsonpath ---------------------------------------------- - $."g"?(("x" >= 123 || "a" == 4) is unknown) +select '$.g ? ((@.x >= 123 || @.a == 4) is unknown)'::jsonpath; + jsonpath +------------------------------------------------- + $."g"?((@."x" >= 123 || @."a" == 4) is unknown) (1 row) select '$.g ? (exists (.x))'::jsonpath; @@ -267,16 +261,16 @@ select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; $."g"?(exists (@."x"?(@ == 14))) (1 row) -select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; - jsonpath --------------------------------------------------------------- - $."g"?(("x" >= 123 || "a" == 4) && exists (@."x"?(@ == 14))) +select '$.g ? ((@.x >= 123 || @.a == 4) && exists (.x ? (@ == 14)))'::jsonpath; + jsonpath +------------------------------------------------------------------ + $."g"?((@."x" >= 123 || @."a" == 4) && exists (@."x"?(@ == 14))) (1 row) -select '$.g ? (+x >= +-(+a + 2))'::jsonpath; - jsonpath --------------------------------- - $."g"?(+"x" >= +(-(+"a" + 2))) +select '$.g ? (+@.x >= +-(+@.a + 2))'::jsonpath; + jsonpath +------------------------------------ + $."g"?(+@."x" >= +(-(+@."a" + 2))) (1 row) select '$a'::jsonpath; @@ -297,10 +291,10 @@ select '$a[*]'::jsonpath; $"a"[*] (1 row) -select '$.g ? (zip == $zip)'::jsonpath; - jsonpath -------------------------- - $."g"?("zip" == $"zip") +select '$.g ? (@.zip == $zip)'::jsonpath; + jsonpath +--------------------------- + $."g"?(@."zip" == $"zip") (1 row) select '$.a.[1,2, 3 to 16]'::jsonpath; @@ -377,12 +371,6 @@ select '"aaa".type()'::jsonpath; "aaa".type() (1 row) -select 'aaa.type()'::jsonpath; - jsonpath --------------- - "aaa".type() -(1 row) - select 'true.type()'::jsonpath; jsonpath ------------- @@ -510,291 +498,291 @@ select '1 + ($.a.b > 2).c.d'::jsonpath; (1 + ($."a"."b" > 2)."c"."d") (1 row) -select '$ ? (a < 1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) -(1 row) - -select '$ ? (a < -1)'::jsonpath; - jsonpath --------------- - $?("a" < -1) -(1 row) - -select '$ ? (a < +1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) -(1 row) - -select '$ ? (a < .1)'::jsonpath; +select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- - $?("a" < 0.1) + $?(@."a" < 1) (1 row) -select '$ ? (a < -.1)'::jsonpath; +select '$ ? (@.a < -1)'::jsonpath; jsonpath ---------------- - $?("a" < -0.1) + $?(@."a" < -1) (1 row) -select '$ ? (a < +.1)'::jsonpath; +select '$ ? (@.a < +1)'::jsonpath; jsonpath --------------- - $?("a" < 0.1) + $?(@."a" < 1) (1 row) -select '$ ? (a < 0.1)'::jsonpath; - jsonpath ---------------- - $?("a" < 0.1) -(1 row) - -select '$ ? (a < -0.1)'::jsonpath; - jsonpath ----------------- - $?("a" < -0.1) -(1 row) - -select '$ ? (a < +0.1)'::jsonpath; - jsonpath ---------------- - $?("a" < 0.1) +select '$ ? (@.a < .1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 0.1) (1 row) -select '$ ? (a < 10.1)'::jsonpath; - jsonpath ----------------- - $?("a" < 10.1) +select '$ ? (@.a < -.1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < -0.1) (1 row) -select '$ ? (a < -10.1)'::jsonpath; +select '$ ? (@.a < +.1)'::jsonpath; jsonpath ----------------- - $?("a" < -10.1) + $?(@."a" < 0.1) (1 row) -select '$ ? (a < +10.1)'::jsonpath; - jsonpath ----------------- - $?("a" < 10.1) -(1 row) - -select '$ ? (a < 1e1)'::jsonpath; - jsonpath --------------- - $?("a" < 10) +select '$ ? (@.a < 0.1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 0.1) (1 row) -select '$ ? (a < -1e1)'::jsonpath; - jsonpath ---------------- - $?("a" < -10) +select '$ ? (@.a < -0.1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < -0.1) (1 row) -select '$ ? (a < +1e1)'::jsonpath; - jsonpath --------------- - $?("a" < 10) +select '$ ? (@.a < +0.1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 0.1) (1 row) -select '$ ? (a < .1e1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < 10.1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 10.1) (1 row) -select '$ ? (a < -.1e1)'::jsonpath; - jsonpath --------------- - $?("a" < -1) +select '$ ? (@.a < -10.1)'::jsonpath; + jsonpath +------------------- + $?(@."a" < -10.1) (1 row) -select '$ ? (a < +.1e1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < +10.1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 10.1) (1 row) -select '$ ? (a < 0.1e1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < 1e1)'::jsonpath; + jsonpath +---------------- + $?(@."a" < 10) (1 row) -select '$ ? (a < -0.1e1)'::jsonpath; - jsonpath --------------- - $?("a" < -1) +select '$ ? (@.a < -1e1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < -10) (1 row) -select '$ ? (a < +0.1e1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < +1e1)'::jsonpath; + jsonpath +---------------- + $?(@."a" < 10) (1 row) -select '$ ? (a < 10.1e1)'::jsonpath; +select '$ ? (@.a < .1e1)'::jsonpath; jsonpath --------------- - $?("a" < 101) + $?(@."a" < 1) (1 row) -select '$ ? (a < -10.1e1)'::jsonpath; +select '$ ? (@.a < -.1e1)'::jsonpath; jsonpath ---------------- - $?("a" < -101) + $?(@."a" < -1) (1 row) -select '$ ? (a < +10.1e1)'::jsonpath; +select '$ ? (@.a < +.1e1)'::jsonpath; jsonpath --------------- - $?("a" < 101) + $?(@."a" < 1) (1 row) -select '$ ? (a < 1e-1)'::jsonpath; +select '$ ? (@.a < 0.1e1)'::jsonpath; jsonpath --------------- - $?("a" < 0.1) + $?(@."a" < 1) (1 row) -select '$ ? (a < -1e-1)'::jsonpath; +select '$ ? (@.a < -0.1e1)'::jsonpath; jsonpath ---------------- - $?("a" < -0.1) + $?(@."a" < -1) (1 row) -select '$ ? (a < +1e-1)'::jsonpath; +select '$ ? (@.a < +0.1e1)'::jsonpath; jsonpath --------------- - $?("a" < 0.1) + $?(@."a" < 1) (1 row) -select '$ ? (a < .1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 0.01) +select '$ ? (@.a < 10.1e1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 101) +(1 row) + +select '$ ? (@.a < -10.1e1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < -101) (1 row) -select '$ ? (a < -.1e-1)'::jsonpath; +select '$ ? (@.a < +10.1e1)'::jsonpath; jsonpath ----------------- - $?("a" < -0.01) + $?(@."a" < 101) (1 row) -select '$ ? (a < +.1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 0.01) +select '$ ? (@.a < 1e-1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 0.1) (1 row) -select '$ ? (a < 0.1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 0.01) +select '$ ? (@.a < -1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < -0.1) (1 row) -select '$ ? (a < -0.1e-1)'::jsonpath; +select '$ ? (@.a < +1e-1)'::jsonpath; jsonpath ----------------- - $?("a" < -0.01) + $?(@."a" < 0.1) (1 row) -select '$ ? (a < +0.1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 0.01) +select '$ ? (@.a < .1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 0.01) (1 row) -select '$ ? (a < 10.1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 1.01) +select '$ ? (@.a < -.1e-1)'::jsonpath; + jsonpath +------------------- + $?(@."a" < -0.01) (1 row) -select '$ ? (a < -10.1e-1)'::jsonpath; - jsonpath ------------------ - $?("a" < -1.01) +select '$ ? (@.a < +.1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 0.01) (1 row) -select '$ ? (a < +10.1e-1)'::jsonpath; - jsonpath ----------------- - $?("a" < 1.01) +select '$ ? (@.a < 0.1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 0.01) (1 row) -select '$ ? (a < 1e+1)'::jsonpath; - jsonpath --------------- - $?("a" < 10) +select '$ ? (@.a < -0.1e-1)'::jsonpath; + jsonpath +------------------- + $?(@."a" < -0.01) (1 row) -select '$ ? (a < -1e+1)'::jsonpath; - jsonpath ---------------- - $?("a" < -10) +select '$ ? (@.a < +0.1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 0.01) (1 row) -select '$ ? (a < +1e+1)'::jsonpath; - jsonpath --------------- - $?("a" < 10) +select '$ ? (@.a < 10.1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 1.01) (1 row) -select '$ ? (a < .1e+1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < -10.1e-1)'::jsonpath; + jsonpath +------------------- + $?(@."a" < -1.01) (1 row) -select '$ ? (a < -.1e+1)'::jsonpath; - jsonpath --------------- - $?("a" < -1) +select '$ ? (@.a < +10.1e-1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < 1.01) (1 row) -select '$ ? (a < +.1e+1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < 1e+1)'::jsonpath; + jsonpath +---------------- + $?(@."a" < 10) (1 row) -select '$ ? (a < 0.1e+1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < -1e+1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < -10) (1 row) -select '$ ? (a < -0.1e+1)'::jsonpath; - jsonpath --------------- - $?("a" < -1) +select '$ ? (@.a < +1e+1)'::jsonpath; + jsonpath +---------------- + $?(@."a" < 10) (1 row) -select '$ ? (a < +0.1e+1)'::jsonpath; - jsonpath -------------- - $?("a" < 1) +select '$ ? (@.a < .1e+1)'::jsonpath; + jsonpath +--------------- + $?(@."a" < 1) +(1 row) + +select '$ ? (@.a < -.1e+1)'::jsonpath; + jsonpath +---------------- + $?(@."a" < -1) (1 row) -select '$ ? (a < 10.1e+1)'::jsonpath; +select '$ ? (@.a < +.1e+1)'::jsonpath; jsonpath --------------- - $?("a" < 101) + $?(@."a" < 1) (1 row) -select '$ ? (a < -10.1e+1)'::jsonpath; +select '$ ? (@.a < 0.1e+1)'::jsonpath; + jsonpath +--------------- + $?(@."a" < 1) +(1 row) + +select '$ ? (@.a < -0.1e+1)'::jsonpath; jsonpath ---------------- - $?("a" < -101) + $?(@."a" < -1) (1 row) -select '$ ? (a < +10.1e+1)'::jsonpath; +select '$ ? (@.a < +0.1e+1)'::jsonpath; jsonpath --------------- - $?("a" < 101) + $?(@."a" < 1) +(1 row) + +select '$ ? (@.a < 10.1e+1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 101) +(1 row) + +select '$ ? (@.a < -10.1e+1)'::jsonpath; + jsonpath +------------------ + $?(@."a" < -101) +(1 row) + +select '$ ? (@.a < +10.1e+1)'::jsonpath; + jsonpath +----------------- + $?(@."a" < 101) (1 row) diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index a5f875ce73..f19caeab89 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -186,7 +186,6 @@ select jsonb 'null' @* 'null.type()'; select jsonb 'null' @* 'true.type()'; select jsonb 'null' @* '123.type()'; select jsonb 'null' @* '"123".type()'; -select jsonb 'null' @* 'aaa.type()'; select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10'; select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10'; diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 40eb4eeb45..4741ba2f0e 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -33,26 +33,25 @@ select '$.a/+-1'::jsonpath; select '$.g ? ($.a == 1)'::jsonpath; select '$.g ? (@ == 1)'::jsonpath; -select '$.g ? (a == 1)'::jsonpath; select '$.g ? (.a == 1)'::jsonpath; select '$.g ? (@.a == 1)'::jsonpath; -select '$.g ? (@.a == 1 || a == 4)'::jsonpath; -select '$.g ? (@.a == 1 && a == 4)'::jsonpath; -select '$.g ? (@.a == 1 || a == 4 && b == 7)'::jsonpath; -select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath; -select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath; +select '$.g ? (@.a == 1 || @.a == 4)'::jsonpath; +select '$.g ? (@.a == 1 && @.a == 4)'::jsonpath; +select '$.g ? (@.a == 1 || @.a == 4 && @.b == 7)'::jsonpath; +select '$.g ? (@.a == 1 || !(@.a == 4) && @.b == 7)'::jsonpath; +select '$.g ? (@.a == 1 || !(@.x >= 123 || @.a == 4) && @.b == 7)'::jsonpath; select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath; -select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath; +select '$.g ? ((@.x >= 123 || @.a == 4) is unknown)'::jsonpath; select '$.g ? (exists (.x))'::jsonpath; select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath; select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath; -select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath; -select '$.g ? (+x >= +-(+a + 2))'::jsonpath; +select '$.g ? ((@.x >= 123 || @.a == 4) && exists (.x ? (@ == 14)))'::jsonpath; +select '$.g ? (+@.x >= +-(+@.a + 2))'::jsonpath; select '$a'::jsonpath; select '$a.b'::jsonpath; select '$a[*]'::jsonpath; -select '$.g ? (zip == $zip)'::jsonpath; +select '$.g ? (@.zip == $zip)'::jsonpath; select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; @@ -67,7 +66,6 @@ select '$[@ ? (last > 0)]'::jsonpath; select 'null.type()'::jsonpath; select '1.type()'::jsonpath; select '"aaa".type()'::jsonpath; -select 'aaa.type()'::jsonpath; select 'true.type()'::jsonpath; select '$.datetime()'::jsonpath; select '$.datetime("datetime template")'::jsonpath; @@ -95,51 +93,51 @@ select '(-+$.a.b).c.d'::jsonpath; select '1 + ($.a.b + 2).c.d'::jsonpath; select '1 + ($.a.b > 2).c.d'::jsonpath; -select '$ ? (a < 1)'::jsonpath; -select '$ ? (a < -1)'::jsonpath; -select '$ ? (a < +1)'::jsonpath; -select '$ ? (a < .1)'::jsonpath; -select '$ ? (a < -.1)'::jsonpath; -select '$ ? (a < +.1)'::jsonpath; -select '$ ? (a < 0.1)'::jsonpath; -select '$ ? (a < -0.1)'::jsonpath; -select '$ ? (a < +0.1)'::jsonpath; -select '$ ? (a < 10.1)'::jsonpath; -select '$ ? (a < -10.1)'::jsonpath; -select '$ ? (a < +10.1)'::jsonpath; -select '$ ? (a < 1e1)'::jsonpath; -select '$ ? (a < -1e1)'::jsonpath; -select '$ ? (a < +1e1)'::jsonpath; -select '$ ? (a < .1e1)'::jsonpath; -select '$ ? (a < -.1e1)'::jsonpath; -select '$ ? (a < +.1e1)'::jsonpath; -select '$ ? (a < 0.1e1)'::jsonpath; -select '$ ? (a < -0.1e1)'::jsonpath; -select '$ ? (a < +0.1e1)'::jsonpath; -select '$ ? (a < 10.1e1)'::jsonpath; -select '$ ? (a < -10.1e1)'::jsonpath; -select '$ ? (a < +10.1e1)'::jsonpath; -select '$ ? (a < 1e-1)'::jsonpath; -select '$ ? (a < -1e-1)'::jsonpath; -select '$ ? (a < +1e-1)'::jsonpath; -select '$ ? (a < .1e-1)'::jsonpath; -select '$ ? (a < -.1e-1)'::jsonpath; -select '$ ? (a < +.1e-1)'::jsonpath; -select '$ ? (a < 0.1e-1)'::jsonpath; -select '$ ? (a < -0.1e-1)'::jsonpath; -select '$ ? (a < +0.1e-1)'::jsonpath; -select '$ ? (a < 10.1e-1)'::jsonpath; -select '$ ? (a < -10.1e-1)'::jsonpath; -select '$ ? (a < +10.1e-1)'::jsonpath; -select '$ ? (a < 1e+1)'::jsonpath; -select '$ ? (a < -1e+1)'::jsonpath; -select '$ ? (a < +1e+1)'::jsonpath; -select '$ ? (a < .1e+1)'::jsonpath; -select '$ ? (a < -.1e+1)'::jsonpath; -select '$ ? (a < +.1e+1)'::jsonpath; -select '$ ? (a < 0.1e+1)'::jsonpath; -select '$ ? (a < -0.1e+1)'::jsonpath; -select '$ ? (a < +0.1e+1)'::jsonpath; -select '$ ? (a < 10.1e+1)'::jsonpath; -select '$ ? (a < -10.1e+1)'::jsonpath; -select '$ ? (a < +10.1e+1)'::jsonpath; +select '$ ? (@.a < 1)'::jsonpath; +select '$ ? (@.a < -1)'::jsonpath; +select '$ ? (@.a < +1)'::jsonpath; +select '$ ? (@.a < .1)'::jsonpath; +select '$ ? (@.a < -.1)'::jsonpath; +select '$ ? (@.a < +.1)'::jsonpath; +select '$ ? (@.a < 0.1)'::jsonpath; +select '$ ? (@.a < -0.1)'::jsonpath; +select '$ ? (@.a < +0.1)'::jsonpath; +select '$ ? (@.a < 10.1)'::jsonpath; +select '$ ? (@.a < -10.1)'::jsonpath; +select '$ ? (@.a < +10.1)'::jsonpath; +select '$ ? (@.a < 1e1)'::jsonpath; +select '$ ? (@.a < -1e1)'::jsonpath; +select '$ ? (@.a < +1e1)'::jsonpath; +select '$ ? (@.a < .1e1)'::jsonpath; +select '$ ? (@.a < -.1e1)'::jsonpath; +select '$ ? (@.a < +.1e1)'::jsonpath; +select '$ ? (@.a < 0.1e1)'::jsonpath; +select '$ ? (@.a < -0.1e1)'::jsonpath; +select '$ ? (@.a < +0.1e1)'::jsonpath; +select '$ ? (@.a < 10.1e1)'::jsonpath; +select '$ ? (@.a < -10.1e1)'::jsonpath; +select '$ ? (@.a < +10.1e1)'::jsonpath; +select '$ ? (@.a < 1e-1)'::jsonpath; +select '$ ? (@.a < -1e-1)'::jsonpath; +select '$ ? (@.a < +1e-1)'::jsonpath; +select '$ ? (@.a < .1e-1)'::jsonpath; +select '$ ? (@.a < -.1e-1)'::jsonpath; +select '$ ? (@.a < +.1e-1)'::jsonpath; +select '$ ? (@.a < 0.1e-1)'::jsonpath; +select '$ ? (@.a < -0.1e-1)'::jsonpath; +select '$ ? (@.a < +0.1e-1)'::jsonpath; +select '$ ? (@.a < 10.1e-1)'::jsonpath; +select '$ ? (@.a < -10.1e-1)'::jsonpath; +select '$ ? (@.a < +10.1e-1)'::jsonpath; +select '$ ? (@.a < 1e+1)'::jsonpath; +select '$ ? (@.a < -1e+1)'::jsonpath; +select '$ ? (@.a < +1e+1)'::jsonpath; +select '$ ? (@.a < .1e+1)'::jsonpath; +select '$ ? (@.a < -.1e+1)'::jsonpath; +select '$ ? (@.a < +.1e+1)'::jsonpath; +select '$ ? (@.a < 0.1e+1)'::jsonpath; +select '$ ? (@.a < -0.1e+1)'::jsonpath; +select '$ ? (@.a < +0.1e+1)'::jsonpath; +select '$ ? (@.a < 10.1e+1)'::jsonpath; +select '$ ? (@.a < -10.1e+1)'::jsonpath; +select '$ ? (@.a < +10.1e+1)'::jsonpath; From 9cad7ae22779384f2e8445c8edef08805a44f3a2 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 20 Jan 2018 00:48:02 +0300 Subject: [PATCH 53/97] Fix jsonpath array accessor syntax --- src/backend/utils/adt/jsonpath_gram.y | 1 - src/test/regress/expected/jsonb_jsonpath.out | 46 ++++++++++---------- src/test/regress/expected/jsonpath.out | 36 --------------- src/test/regress/sql/jsonb_jsonpath.sql | 46 ++++++++++---------- src/test/regress/sql/jsonpath.sql | 6 --- 5 files changed, 46 insertions(+), 89 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 11b1fcf7e5..b8139c5add 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -431,7 +431,6 @@ accessor_op: '.' key { $$ = $2; } | '.' '*' { $$ = makeItemType(jpiAnyKey); } | array_accessor { $$ = $1; } - | '.' array_accessor { $$ = $2; } | '.' any_path { $$ = $2; } | '.' method '(' ')' { $$ = makeItemType($2); } | '.' DATETIME_P '(' opt_datetime_template ')' diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 10d2c59d3a..428d45d8c2 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -58,25 +58,25 @@ select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}'; f (1 row) -select jsonb '[]' @? '$.[*]'; +select jsonb '[]' @? '$[*]'; ?column? ---------- f (1 row) -select jsonb '[1]' @? '$.[*]'; +select jsonb '[1]' @? '$[*]'; ?column? ---------- t (1 row) -select jsonb '[1]' @? '$.[1]'; +select jsonb '[1]' @? '$[1]'; ?column? ---------- f (1 row) -select jsonb '[1]' @? 'strict $.[1]'; +select jsonb '[1]' @? 'strict $[1]'; ?column? ---------- @@ -84,37 +84,37 @@ select jsonb '[1]' @? 'strict $.[1]'; select jsonb '[1]' @* 'strict $[1]'; ERROR: Invalid SQL/JSON subscript -select jsonb '[1]' @? '$.[0]'; +select jsonb '[1]' @? '$[0]'; ?column? ---------- t (1 row) -select jsonb '[1]' @? '$.[0.3]'; +select jsonb '[1]' @? '$[0.3]'; ?column? ---------- t (1 row) -select jsonb '[1]' @? '$.[0.5]'; +select jsonb '[1]' @? '$[0.5]'; ?column? ---------- t (1 row) -select jsonb '[1]' @? '$.[0.9]'; +select jsonb '[1]' @? '$[0.9]'; ?column? ---------- t (1 row) -select jsonb '[1]' @? '$.[1.2]'; +select jsonb '[1]' @? '$[1.2]'; ?column? ---------- f (1 row) -select jsonb '[1]' @? 'strict $.[1.2]'; +select jsonb '[1]' @? 'strict $[1.2]'; ?column? ---------- @@ -193,48 +193,48 @@ select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; 13 (1 row) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a'; ?column? ---------- 13 (1 row) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*'; ?column? ---------- 13 14 (2 rows) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a'; ?column? ---------- (0 rows) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a'; ?column? ---------- 13 (1 row) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a'; ?column? ---------- (0 rows) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a'; ?column? ---------- 13 (1 row) -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; ?column? ---------- 13 (1 row) -select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]'; +select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]'; ?column? ----------- {"a": 13} @@ -332,7 +332,7 @@ select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" 10 (1 row) -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); jsonpath_query ---------------- 10 @@ -340,14 +340,14 @@ select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)' 12 (3 rows) -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}'); jsonpath_query ---------------- 10 11 (2 rows) -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); jsonpath_query ---------------- 10 @@ -355,13 +355,13 @@ select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $va 12 (3 rows) -select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); jsonpath_query ---------------- "1" (1 row) -select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); jsonpath_query ---------------- "1" diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 5731263f7c..219ff510f9 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -39,54 +39,24 @@ select '$.a.*'::jsonpath; $."a".* (1 row) -select '$.*.[*]'::jsonpath; - jsonpath ----------- - $.*[*] -(1 row) - select '$.*[*]'::jsonpath; jsonpath ---------- $.*[*] (1 row) -select '$.a.[*]'::jsonpath; - jsonpath ----------- - $."a"[*] -(1 row) - select '$.a[*]'::jsonpath; jsonpath ---------- $."a"[*] (1 row) -select '$.a.[*][*]'::jsonpath; - jsonpath -------------- - $."a"[*][*] -(1 row) - -select '$.a.[*].[*]'::jsonpath; - jsonpath -------------- - $."a"[*][*] -(1 row) - select '$.a[*][*]'::jsonpath; jsonpath ------------- $."a"[*][*] (1 row) -select '$.a[*].[*]'::jsonpath; - jsonpath -------------- - $."a"[*][*] -(1 row) - select '$[*]'::jsonpath; jsonpath ---------- @@ -297,12 +267,6 @@ select '$.g ? (@.zip == $zip)'::jsonpath; $."g"?(@."zip" == $"zip") (1 row) -select '$.a.[1,2, 3 to 16]'::jsonpath; - jsonpath --------------------- - $."a"[1,2,3 to 16] -(1 row) - select '$.a[1,2, 3 to 16]'::jsonpath; jsonpath -------------------- diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index f19caeab89..dc3bc85e71 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -8,17 +8,17 @@ select jsonb '{"a": 1}' @? '$.*'; select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}'; select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}'; select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}'; -select jsonb '[]' @? '$.[*]'; -select jsonb '[1]' @? '$.[*]'; -select jsonb '[1]' @? '$.[1]'; -select jsonb '[1]' @? 'strict $.[1]'; +select jsonb '[]' @? '$[*]'; +select jsonb '[1]' @? '$[*]'; +select jsonb '[1]' @? '$[1]'; +select jsonb '[1]' @? 'strict $[1]'; select jsonb '[1]' @* 'strict $[1]'; -select jsonb '[1]' @? '$.[0]'; -select jsonb '[1]' @? '$.[0.3]'; -select jsonb '[1]' @? '$.[0.5]'; -select jsonb '[1]' @? '$.[0.9]'; -select jsonb '[1]' @? '$.[1.2]'; -select jsonb '[1]' @? 'strict $.[1.2]'; +select jsonb '[1]' @? '$[0]'; +select jsonb '[1]' @? '$[0.3]'; +select jsonb '[1]' @? '$[0.5]'; +select jsonb '[1]' @? '$[0.9]'; +select jsonb '[1]' @? '$[1.2]'; +select jsonb '[1]' @? 'strict $[1.2]'; 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[*])'; @@ -32,14 +32,14 @@ select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a'; select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b'; select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*'; select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a'; -select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a'; -select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a'; +select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; +select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]'; select jsonb '1' @* 'lax $[0]'; select jsonb '1' @* 'lax $[*]'; select jsonb '[1]' @* 'lax $[0]'; @@ -58,11 +58,11 @@ select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)'); select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}'); -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}'); -select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}'); -select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")'); -select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); +select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 4741ba2f0e..3d94016ec6 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -7,14 +7,9 @@ select 'lax $'::jsonpath; select '$.a'::jsonpath; select '$.a.v'::jsonpath; select '$.a.*'::jsonpath; -select '$.*.[*]'::jsonpath; select '$.*[*]'::jsonpath; -select '$.a.[*]'::jsonpath; select '$.a[*]'::jsonpath; -select '$.a.[*][*]'::jsonpath; -select '$.a.[*].[*]'::jsonpath; select '$.a[*][*]'::jsonpath; -select '$.a[*].[*]'::jsonpath; select '$[*]'::jsonpath; select '$[0]'::jsonpath; select '$[*][0]'::jsonpath; @@ -52,7 +47,6 @@ select '$a'::jsonpath; select '$a.b'::jsonpath; select '$a[*]'::jsonpath; select '$.g ? (@.zip == $zip)'::jsonpath; -select '$.a.[1,2, 3 to 16]'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; select '$.a[$.a.size() - 3]'::jsonpath; From 8fa4e08180d494124cde2b426bc97703ec1c733c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 13 Feb 2018 23:16:34 +0300 Subject: [PATCH 54/97] Fix jsonpath parenthesized expressions --- src/backend/utils/adt/jsonpath_gram.y | 40 ++++++++++++-------------- src/test/regress/expected/jsonpath.out | 18 ++++++++++++ src/test/regress/sql/jsonpath.sql | 3 ++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index b8139c5add..8fc4cad875 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -281,7 +281,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type result -%type scalar_value path_primary expr pexpr array_accessor +%type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial opt_datetime_template expr_or_predicate @@ -348,21 +348,21 @@ comp_op: ; delimited_predicate: - '(' predicate ')' { $$ = $2; } + '(' predicate ')' { $$ = $2; } | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); } ; predicate: delimited_predicate { $$ = $1; } - | pexpr comp_op pexpr { $$ = makeItemBinary($2, $1, $3); } + | expr comp_op expr { $$ = makeItemBinary($2, $1, $3); } | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); } | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); } | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); } | '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); } - | pexpr STARTS_P WITH_P starts_with_initial + | expr STARTS_P WITH_P starts_with_initial { $$ = makeItemBinary(jpiStartsWith, $1, $4); } - | pexpr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }; - | pexpr LIKE_REGEX_P STRING_P FLAG_P STRING_P + | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }; + | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P { $$ = makeItemLikeRegex($1, &$3, &$5); }; ; @@ -382,29 +382,25 @@ accessor_expr: path_primary { $$ = list_make1($1); } | '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); } | '(' expr ')' accessor_op { $$ = list_make2($2, $4); } - | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); } + | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); } | accessor_expr accessor_op { $$ = lappend($1, $2); } ; -pexpr: - expr { $$ = $1; } - | '(' expr ')' { $$ = $2; } - ; - expr: - accessor_expr { $$ = makeItemList($1); } - | '+' pexpr %prec UMINUS { $$ = makeItemUnary(jpiPlus, $2); } - | '-' pexpr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); } - | pexpr '+' pexpr { $$ = makeItemBinary(jpiAdd, $1, $3); } - | pexpr '-' pexpr { $$ = makeItemBinary(jpiSub, $1, $3); } - | pexpr '*' pexpr { $$ = makeItemBinary(jpiMul, $1, $3); } - | pexpr '/' pexpr { $$ = makeItemBinary(jpiDiv, $1, $3); } - | pexpr '%' pexpr { $$ = makeItemBinary(jpiMod, $1, $3); } + accessor_expr { $$ = makeItemList($1); } + | '(' expr ')' { $$ = $2; } + | '+' expr %prec UMINUS { $$ = makeItemUnary(jpiPlus, $2); } + | '-' expr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); } + | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); } + | expr '-' expr { $$ = makeItemBinary(jpiSub, $1, $3); } + | expr '*' expr { $$ = makeItemBinary(jpiMul, $1, $3); } + | expr '/' expr { $$ = makeItemBinary(jpiDiv, $1, $3); } + | expr '%' expr { $$ = makeItemBinary(jpiMod, $1, $3); } ; index_elem: - pexpr { $$ = makeItemBinary(jpiSubscript, $1, NULL); } - | pexpr TO_P pexpr { $$ = makeItemBinary(jpiSubscript, $1, $3); } + expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); } + | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); } ; index_list: diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 219ff510f9..0b2f1bb15e 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -462,6 +462,24 @@ select '1 + ($.a.b > 2).c.d'::jsonpath; (1 + ($."a"."b" > 2)."c"."d") (1 row) +select '($)'::jsonpath; + jsonpath +---------- + $ +(1 row) + +select '(($))'::jsonpath; + jsonpath +---------- + $ +(1 row) + +select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath; + jsonpath +------------------------------------------------- + (($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c"))) +(1 row) + select '$ ? (@.a < 1)'::jsonpath; jsonpath --------------- diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 3d94016ec6..32c33f150f 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -86,6 +86,9 @@ select '($.a.b + -$.x.y).c.d'::jsonpath; select '(-+$.a.b).c.d'::jsonpath; select '1 + ($.a.b + 2).c.d'::jsonpath; select '1 + ($.a.b > 2).c.d'::jsonpath; +select '($)'::jsonpath; +select '(($))'::jsonpath; +select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath; select '$ ? (@.a < 1)'::jsonpath; select '$ ? (@.a < -1)'::jsonpath; From d7ad18035cfab30b49b4692efe88b69da740ee2f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 15 Feb 2018 20:29:42 +0300 Subject: [PATCH 55/97] Fix jsonpath handling of arithmetic errors --- src/backend/utils/adt/jsonpath_exec.c | 32 +++++++++++++++++--- src/test/regress/expected/jsonb_jsonpath.out | 17 +++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 5 +++ 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index c05a6ffee3..c6d7168f3d 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -843,6 +843,7 @@ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { + MemoryContext mcxt = CurrentMemoryContext; JsonPathExecResult jper; JsonPathItem elem; JsonValueList lseq = { 0 }; @@ -851,6 +852,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *rval; JsonbValue lvalbuf; JsonbValue rvalbuf; + PGFunction func; Datum ldatum; Datum rdatum; Datum res; @@ -899,23 +901,43 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, switch (jsp->type) { case jpiAdd: - res = DirectFunctionCall2(numeric_add, ldatum, rdatum); + func = numeric_add; break; case jpiSub: - res = DirectFunctionCall2(numeric_sub, ldatum, rdatum); + func = numeric_sub; break; case jpiMul: - res = DirectFunctionCall2(numeric_mul, ldatum, rdatum); + func = numeric_mul; break; case jpiDiv: - res = DirectFunctionCall2(numeric_div, ldatum, rdatum); + func = numeric_div; break; case jpiMod: - res = DirectFunctionCall2(numeric_mod, ldatum, rdatum); + func = numeric_mod; break; default: elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); + func = NULL; + break; + } + + PG_TRY(); + { + res = DirectFunctionCall2(func, ldatum, rdatum); + } + PG_CATCH(); + { + int errcode = geterrcode(); + + if (ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION) + PG_RE_THROW(); + + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + + return jperMakeError(errcode); } + PG_END_TRY(); lval = palloc(sizeof(*lval)); lval->type = jbvNumeric; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 428d45d8c2..5c703d63f4 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -777,6 +777,23 @@ select jsonb '1' @? '$ ? ($ > 0)'; t (1 row) +-- arithmetic errors +select jsonb '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)'; + ?column? +---------- + 1 + 2 + 3 +(3 rows) + +select jsonb '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)'; + ?column? +---------- + 0 +(1 row) + +select jsonb '0' @* '1 / $'; +ERROR: Unknown SQL/JSON error -- unwrapping of operator arguments in lax mode select jsonb '{"a": [2]}' @* 'lax $.a * 3'; ?column? diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index dc3bc85e71..f0b798df47 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -156,6 +156,11 @@ select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)'; select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)'; select jsonb '1' @? '$ ? ($ > 0)'; +-- arithmetic errors +select jsonb '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)'; +select jsonb '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)'; +select jsonb '0' @* '1 / $'; + -- unwrapping of operator arguments in lax mode select jsonb '{"a": [2]}' @* 'lax $.a * 3'; select jsonb '{"a": [2]}' @* 'lax $.a + 3'; From c755a7fc24c9400b7728bddbfc0d2587a6026d18 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 02:45:32 +0300 Subject: [PATCH 56/97] Fix jsonpath timestamptz encoding --- src/backend/utils/adt/formatting.c | 43 ++++--- src/backend/utils/adt/json.c | 29 ++++- src/backend/utils/adt/jsonb.c | 6 +- src/backend/utils/adt/jsonb_util.c | 3 +- src/backend/utils/adt/jsonpath.c | 43 +++---- src/backend/utils/adt/jsonpath_exec.c | 118 +++++++++++-------- src/backend/utils/adt/jsonpath_gram.y | 12 +- src/include/utils/formatting.h | 4 +- src/include/utils/jsonapi.h | 2 +- src/include/utils/jsonb.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 68 +++++++---- src/test/regress/sql/jsonb_jsonpath.sql | 7 ++ 12 files changed, 215 insertions(+), 121 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index a9c9bb7279..41abf3d747 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -3748,8 +3748,8 @@ to_date(PG_FUNCTION_ARGS) * presence of date/time/zone components in the format string. */ Datum -to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, - Oid *typid, int32 *typmod) +to_datetime(text *date_txt, const char *fmt, int fmt_len, char *tzname, + bool strict, Oid *typid, int32 *typmod, int *tz) { struct pg_tm tm; fsec_t fsec; @@ -3758,6 +3758,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags); *typmod = -1; /* TODO implement FF1, ..., FF9 */ + *tz = 0; if (flags & DCH_DATED) { @@ -3766,20 +3767,27 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, if (flags & DCH_ZONED) { TimestampTz result; - int tz; if (tm.tm_zone) + tzname = (char *) tm.tm_zone; + + if (tzname) { - int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); + int dterr = DecodeTimezone(tzname, tz); if (dterr) - DateTimeParseError(dterr, text_to_cstring(date_txt), - "timestamptz"); + DateTimeParseError(dterr, tzname, "timestamptz"); } else - tz = DetermineTimeZoneOffset(&tm, session_timezone); + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("missing time-zone in timestamptz input string"))); - if (tm2timestamp(&tm, fsec, &tz, &result) != 0) + *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"))); @@ -3843,20 +3851,27 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, if (flags & DCH_ZONED) { TimeTzADT *result = palloc(sizeof(TimeTzADT)); - int tz; if (tm.tm_zone) + tzname = (char *) tm.tm_zone; + + if (tzname) { - int dterr = DecodeTimezone((char *) tm.tm_zone, &tz); + int dterr = DecodeTimezone(tzname, tz); if (dterr) - DateTimeParseError(dterr, text_to_cstring(date_txt), - "timetz"); + DateTimeParseError(dterr, tzname, "timetz"); } else - tz = DetermineTimeZoneOffset(&tm, session_timezone); + { + 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) + if (tm2timetz(&tm, fsec, *tz, result) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timetz out of range"))); diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 6f0fe94d63..79eeac76ba 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; @@ -1553,7 +1553,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result, * optionally preallocated buffer 'buf'. */ char * -JsonEncodeDateTime(char *buf, Datum value, Oid typid) +JsonEncodeDateTime(char *buf, Datum value, Oid typid, int *tzp) { if (!buf) buf = palloc(MAXDATELEN + 1); @@ -1630,11 +1630,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 4c79a5e3d3..9d0582cad0 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -794,17 +794,17 @@ 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 2eaac6ef75..89168fb4ca 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -1752,7 +1752,8 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal) JsonEncodeDateTime(buf, scalarVal->val.datetime.value, - scalarVal->val.datetime.typid); + scalarVal->val.datetime.typid, + &scalarVal->val.datetime.tz); len = strlen(buf); appendToBuffer(buffer, buf, len); diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 1d51d8b7a5..ffcdf25d30 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -76,6 +76,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiDiv: case jpiMod: case jpiStartsWith: + case jpiDatetime: { int32 left, right; @@ -89,13 +90,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); - chld = flattenJsonPathParseItem(buf, item->value.args.left, - allowCurrent, - insideArraySubscript); + chld = !item->value.args.left ? 0 : + flattenJsonPathParseItem(buf, item->value.args.left, + allowCurrent, + insideArraySubscript); *(int32*)(buf->data + left) = chld; - chld = flattenJsonPathParseItem(buf, item->value.args.right, - allowCurrent, - insideArraySubscript); + + chld = !item->value.args.right ? 0 : + flattenJsonPathParseItem(buf, item->value.args.right, + allowCurrent, + insideArraySubscript); *(int32*)(buf->data + right) = chld; } break; @@ -122,15 +126,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, *(int32 *)(buf->data + offs) = chld; } break; - case jpiDatetime: - if (!item->value.arg) - { - int32 arg = 0; - - appendBinaryStringInfo(buf, (char *) &arg, sizeof(arg)); - break; - } - /* fall through */ case jpiFilter: case jpiIsUnknown: case jpiNot: @@ -541,10 +536,17 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket break; case jpiDatetime: appendBinaryStringInfo(buf, ".datetime(", 10); - if (v->content.arg) + if (v->content.args.left) { - jspGetArg(v, &elem); + 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; @@ -668,6 +670,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; @@ -683,7 +686,6 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiPlus: case jpiMinus: case jpiFilter: - case jpiDatetime: read_int32(v->content.arg, base, pos); break; case jpiIndexArray: @@ -709,8 +711,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiIsUnknown || v->type == jpiExists || v->type == jpiPlus || - v->type == jpiMinus || - v->type == jpiDatetime + v->type == jpiMinus ); jspInitByBuffer(a, v->base, v->content.arg); @@ -790,6 +791,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || + v->type == jpiDatetime || v->type == jpiStartsWith ); @@ -813,6 +815,7 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || + v->type == jpiDatetime || v->type == jpiStartsWith ); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index c6d7168f3d..fd803cca0b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -266,6 +266,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) value->type = jbvDatetime; value->val.datetime.typid = var->typid; value->val.datetime.typmod = var->typmod; + value->val.datetime.tz = 0; value->val.datetime.value = computedValue; break; case JSONBOID: @@ -1242,16 +1243,22 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, return jperNotFound; } +/* + * Try to parse datetime text with the specified datetime template and + * default time-zone 'tzname'. + * Returns 'value' datum, its type 'typid' and 'typmod'. + */ static bool -tryToParseDatetime(const char *template, text *datetime, - Datum *value, Oid *typid, int32 *typmod) +tryToParseDatetime(const char *fmt, int fmtlen, text *datetime, char *tzname, + bool strict, Datum *value, Oid *typid, int32 *typmod, int *tz) { MemoryContext mcxt = CurrentMemoryContext; bool ok = false; PG_TRY(); { - *value = to_datetime(datetime, template, -1, true, typid, typmod); + *value = to_datetime(datetime, fmt, fmtlen, tzname, strict, + typid, typmod, tz); ok = true; } PG_CATCH(); @@ -1856,83 +1863,95 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, { JsonbValue jbvbuf; Datum value; - text *datetime_txt; + text *datetime; Oid typid; int32 typmod = -1; + int tz; bool hasNext; if (JsonbType(jb) == jbvScalar) jb = JsonbExtractScalar(jb->val.binary.data, &jbvbuf); + res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + if (jb->type != jbvString) - { - res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); break; - } - - datetime_txt = cstring_to_text_with_len(jb->val.string.val, - jb->val.string.len); - res = jperOk; + datetime = cstring_to_text_with_len(jb->val.string.val, + jb->val.string.len); - if (jsp->content.arg) + if (jsp->content.args.left) { - text *template_txt; char *template_str; int template_len; - MemoryContext mcxt = CurrentMemoryContext; + char *tzname = NULL; - jspGetArg(jsp, &elem); + jspGetLeftArg(jsp, &elem); if (elem.type != jpiString) elog(ERROR, "invalid jsonpath item type for .datetime() argument"); template_str = jspGetString(&elem, &template_len); - template_txt = cstring_to_text_with_len(template_str, - template_len); - PG_TRY(); + if (jsp->content.args.right) { - value = to_datetime(datetime_txt, - template_str, template_len, - false, - &typid, &typmod); - } - PG_CATCH(); - { - if (ERRCODE_TO_CATEGORY(geterrcode()) != - ERRCODE_DATA_EXCEPTION) - PG_RE_THROW(); + JsonValueList tzlist = { 0 }; + JsonPathExecResult tzres; + JsonbValue *tzjbv; + + jspGetRightArg(jsp, &elem); + tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb, + &tzlist, false); + + if (jperIsError(tzres)) + return tzres; - FlushErrorState(); - MemoryContextSwitchTo(mcxt); + if (JsonValueListLength(&tzlist) != 1) + break; + + tzjbv = JsonValueListHead(&tzlist); + + if (tzjbv->type != jbvString) + break; - res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + tzname = pnstrdup(tzjbv->val.string.val, + tzjbv->val.string.len); } - PG_END_TRY(); - pfree(template_txt); + if (tryToParseDatetime(template_str, template_len, datetime, + tzname, false, + &value, &typid, &typmod, &tz)) + res = jperOk; + + if (tzname) + pfree(tzname); } else { - if (!tryToParseDatetime("yyyy-mm-dd HH24:MI:SS TZH:TZM", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("yyyy-mm-dd HH24:MI:SS TZH", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("yyyy-mm-dd HH24:MI:SS", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("yyyy-mm-dd", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("HH24:MI:SS TZH:TZM", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("HH24:MI:SS TZH", - datetime_txt, &value, &typid, &typmod) && - !tryToParseDatetime("HH24:MI:SS", - datetime_txt, &value, &typid, &typmod)) - res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); + const char *templates[] = { + "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" + }; + int i; + + for (i = 0; i < sizeof(templates) / sizeof(*templates); i++) + { + if (tryToParseDatetime(templates[i], -1, datetime, + NULL, true, &value, &typid, + &typmod, &tz)) + { + res = jperOk; + break; + } + } } - pfree(datetime_txt); + pfree(datetime); if (jperIsError(res)) break; @@ -1948,6 +1967,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jb->val.datetime.value = value; jb->val.datetime.typid = typid; jb->val.datetime.typmod = typmod; + jb->val.datetime.tz = tz; res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext); } diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 8fc4cad875..7300f76588 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -283,7 +283,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate - index_elem starts_with_initial opt_datetime_template + index_elem starts_with_initial datetime_template opt_datetime_template expr_or_predicate %type accessor_expr @@ -430,12 +430,18 @@ accessor_op: | '.' any_path { $$ = $2; } | '.' method '(' ')' { $$ = makeItemType($2); } | '.' DATETIME_P '(' opt_datetime_template ')' - { $$ = makeItemUnary(jpiDatetime, $4); } + { $$ = makeItemBinary(jpiDatetime, $4, NULL); } + | '.' DATETIME_P '(' datetime_template ',' expr ')' + { $$ = makeItemBinary(jpiDatetime, $4, $6); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } ; -opt_datetime_template: +datetime_template: STRING_P { $$ = makeItemString(&$1); } + ; + +opt_datetime_template: + datetime_template { $$ = $1; } | /* EMPTY */ { $$ = NULL; } ; diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h index 208cc000d3..6db5b3fd89 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, const char *fmt, int fmt_len, - bool strict, Oid *typid, int32 *typmod); +extern Datum to_datetime(text *datetxt, const char *fmt, int fmt_len, char *tzn, + bool strict, Oid *typid, int32 *typmod, int *tz); #endif diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h index 6b483a15a6..803ff667cc 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -161,6 +161,6 @@ 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, int *tz); #endif /* JSONAPI_H */ diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 58b99002a4..b306b2613c 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -287,6 +287,7 @@ struct JsonbValue Datum value; Oid typid; int32 typmod; + int tz; } datetime; } val; }; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 5c703d63f4..033e5afa69 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1234,33 +1234,49 @@ select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; (1 row) select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'; ?column? ----------------------------- "2017-03-10T12:34:00+00:00" (1 row) +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'; + ?column? +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'; + ?column? +-------------------------------- + "2017-03-10T12:34:00-00:12:34" +(1 row) + +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'; +ERROR: Invalid argument for SQL/JSON datetime function select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T07:34:00+00:00" + "2017-03-10T12:34:00+05:00" (1 row) select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T17:34:00+00:00" + "2017-03-10T12:34:00-05:00" (1 row) select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T07:14:00+00:00" + "2017-03-10T12:34:00+05:20" (1 row) select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T17:54:00+00:00" + "2017-03-10T12:34:00-05:20" (1 row) select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; @@ -1270,6 +1286,8 @@ select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; (1 row) select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH", "+00")'; ?column? ------------------ "12:34:00+00:00" @@ -1307,6 +1325,8 @@ select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; (1 row) select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'; ?column? ----------------------------- "2017-03-10T12:34:00+10:00" @@ -1315,25 +1335,25 @@ select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH" select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T17:34:00+10:00" + "2017-03-10T12:34:00+05:00" (1 row) select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-11T03:34:00+10:00" + "2017-03-10T12:34:00-05:00" (1 row) select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T17:14:00+10:00" + "2017-03-10T12:34:00+05:20" (1 row) select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-11T03:54:00+10:00" + "2017-03-10T12:34:00-05:20" (1 row) select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; @@ -1343,6 +1363,8 @@ select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; (1 row) select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH", "+10")'; ?column? ------------------ "12:34:00+10:00" @@ -1406,7 +1428,7 @@ select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; ?column? ----------------------------- - "2017-03-10T01:34:56-08:00" + "2017-03-10T12:34:56+03:00" (1 row) select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; @@ -1418,7 +1440,7 @@ select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; ?column? ----------------------------- - "2017-03-10T01:24:56-08:00" + "2017-03-10T12:34:56+03:10" (1 row) select jsonb '"12:34:56"' @* '$.datetime().type()'; @@ -1466,7 +1488,7 @@ select jsonb ----------------------------- "2017-03-10" "2017-03-10T00:00:00" - "2017-03-10T00:00:00+00:00" + "2017-03-10T03:00:00+03:00" (3 rows) select jsonb @@ -1478,7 +1500,7 @@ select jsonb "2017-03-11" "2017-03-10T00:00:00" "2017-03-10T12:34:56" - "2017-03-10T00:00:00+00:00" + "2017-03-10T03:00:00+03:00" (5 rows) select jsonb @@ -1487,7 +1509,7 @@ select jsonb ?column? ----------------------------- "2017-03-09" - "2017-03-09T21:02:03+00:00" + "2017-03-10T01:02:03+04:00" (2 rows) -- time comparison @@ -1558,7 +1580,7 @@ select jsonb ?column? ----------------------------- "2017-03-10T12:35:00" - "2017-03-10T12:35:00+00:00" + "2017-03-10T13:35:00+01:00" (2 rows) select jsonb @@ -1568,8 +1590,8 @@ select jsonb ----------------------------- "2017-03-10T12:35:00" "2017-03-10T12:36:00" - "2017-03-10T12:35:00+00:00" - "2017-03-10T13:35:00+00:00" + "2017-03-10T13:35:00+01:00" + "2017-03-10T12:35:00-01:00" "2017-03-11" (5 rows) @@ -1579,7 +1601,7 @@ select jsonb ?column? ----------------------------- "2017-03-10T12:34:00" - "2017-03-10T11:35:00+00:00" + "2017-03-10T12:35:00+01:00" "2017-03-10" (3 rows) @@ -1589,7 +1611,7 @@ select jsonb '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:35:00+00:00" + "2017-03-10T12:35:00+01:00" "2017-03-10T11:35:00" (2 rows) @@ -1598,9 +1620,9 @@ select jsonb '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:35:00+00:00" - "2017-03-10T11:36:00+00:00" - "2017-03-10T14:35:00+00:00" + "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" @@ -1611,8 +1633,8 @@ select jsonb '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:34:00+00:00" - "2017-03-10T10:35:00+00:00" + "2017-03-10T12:34:00+01:00" + "2017-03-10T12:35:00+02:00" "2017-03-10T10:35:00" "2017-03-10" (4 rows) diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index f0b798df47..8b36a30049 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -258,12 +258,17 @@ set time zone '+00'; select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'; select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH", "+00")'; select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; @@ -273,12 +278,14 @@ set time zone '+10'; select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'; select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select jsonb '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select jsonb '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select jsonb '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select jsonb '"12:34"' @* '$.datetime("HH24:MI")'; select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH", "+10")'; select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; From 92d88acea0066745611498b0136edbdf031e3092 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 8 Mar 2018 01:37:57 +0300 Subject: [PATCH 57/97] Change syntax of jsonpath .** accessor --- src/backend/utils/adt/jsonpath.c | 19 +++++--- src/backend/utils/adt/jsonpath_exec.c | 4 +- src/backend/utils/adt/jsonpath_gram.y | 19 ++++---- src/test/regress/expected/jsonb_jsonpath.out | 48 +++++++++++++------- src/test/regress/expected/jsonpath.out | 26 +++++------ src/test/regress/sql/jsonb_jsonpath.sql | 36 ++++++++------- src/test/regress/sql/jsonpath.sql | 8 ++-- 7 files changed, 93 insertions(+), 67 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index ffcdf25d30..d07423b8ea 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -504,16 +504,21 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket appendStringInfoChar(buf, '.'); if (v->content.anybounds.first == 0 && - v->content.anybounds.last == PG_UINT32_MAX) + v->content.anybounds.last == PG_UINT32_MAX) appendBinaryStringInfo(buf, "**", 2); - else if (v->content.anybounds.first == 0) - appendStringInfo(buf, "**{,%u}", v->content.anybounds.last); - else if (v->content.anybounds.last == PG_UINT32_MAX) - appendStringInfo(buf, "**{%u,}", v->content.anybounds.first); else if (v->content.anybounds.first == v->content.anybounds.last) - appendStringInfo(buf, "**{%u}", v->content.anybounds.first); + { + if (v->content.anybounds.first == PG_UINT32_MAX) + appendStringInfo(buf, "**{last}"); + else + appendStringInfo(buf, "**{%u}", v->content.anybounds.first); + } + else if (v->content.anybounds.first == PG_UINT32_MAX) + appendStringInfo(buf, "**{last to %u}", v->content.anybounds.last); + else if (v->content.anybounds.last == PG_UINT32_MAX) + appendStringInfo(buf, "**{%u to last}", v->content.anybounds.first); else - appendStringInfo(buf, "**{%u,%u}", v->content.anybounds.first, + appendStringInfo(buf, "**{%u to %u}", v->content.anybounds.first, v->content.anybounds.last); break; case jpiType: diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index fd803cca0b..1fd33f4571 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1047,7 +1047,9 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (r == WJB_VALUE || r == WJB_ELEM) { - if (level >= first) + if (level >= first || + (first == PG_UINT32_MAX && last == PG_UINT32_MAX && + v.type != jbvBinary)) /* leaves only requested */ { /* check expression */ res = recursiveExecuteNext(cxt, NULL, jsp, &v, found, true); diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 7300f76588..6c409a47b8 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -199,7 +199,7 @@ makeAny(int first, int last) { JsonPathParseItem *v = makeItemType(jpiAny); - v->value.anybounds.first = (first > 0) ? first : 0; + v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX; v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX; return v; @@ -269,6 +269,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) JsonPathParseResult *result; JsonPathItemType optype; bool boolean; + int integer; } %token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P @@ -296,6 +297,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type key_name +%type any_level %left OR_P %left AND_P @@ -413,14 +415,15 @@ array_accessor: | '[' index_list ']' { $$ = makeIndexArray($2); } ; +any_level: + INT_P { $$ = pg_atoi($1.val, 4, 0); } + | LAST_P { $$ = -1; } + ; + any_path: - ANY_P { $$ = makeAny(-1, -1); } - | ANY_P '{' INT_P '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), - pg_atoi($3.val, 4, 0)); } - | ANY_P '{' ',' INT_P '}' { $$ = makeAny(-1, pg_atoi($4.val, 4, 0)); } - | ANY_P '{' INT_P ',' '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), -1); } - | ANY_P '{' INT_P ',' INT_P '}' { $$ = makeAny(pg_atoi($3.val, 4, 0), - pg_atoi($5.val, 4, 0)); } + ANY_P { $$ = makeAny(0, -1); } + | ANY_P '{' any_level '}' { $$ = makeAny($3, $3); } + | ANY_P '{' any_level TO_P any_level '}' { $$ = makeAny($3, $5); } ; accessor_op: diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 033e5afa69..8a30e9c763 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -388,13 +388,27 @@ select jsonb '{"a": {"b": 1}}' @* 'lax $.**'; 1 (3 rows) +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}'; + ?column? +----------------- + {"a": {"b": 1}} +(1 row) + +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}'; + ?column? +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}'; ?column? ---------- {"b": 1} (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}'; ?column? ---------- {"b": 1} @@ -407,13 +421,13 @@ select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}'; 1 (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}'; ?column? ---------- (0 rows) @@ -435,19 +449,19 @@ select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; 1 (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; ?column? ---------- 1 @@ -469,25 +483,25 @@ select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; ---------- (0 rows) -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)'; ?column? ---------- 1 @@ -511,19 +525,19 @@ select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; t (1 row) -select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; ?column? ---------- t @@ -547,25 +561,25 @@ select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; f (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)'; ?column? ---------- t diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 0b2f1bb15e..781e3f4344 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -99,28 +99,28 @@ select '$.a.**{2}.b'::jsonpath; $."a".**{2}."b" (1 row) -select '$.a.**{2,2}.b'::jsonpath; +select '$.a.**{2 to 2}.b'::jsonpath; jsonpath ----------------- $."a".**{2}."b" (1 row) -select '$.a.**{2,5}.b'::jsonpath; - jsonpath -------------------- - $."a".**{2,5}."b" +select '$.a.**{2 to 5}.b'::jsonpath; + jsonpath +---------------------- + $."a".**{2 to 5}."b" (1 row) -select '$.a.**{,5}.b'::jsonpath; - jsonpath ------------------- - $."a".**{,5}."b" +select '$.a.**{0 to 5}.b'::jsonpath; + jsonpath +---------------------- + $."a".**{0 to 5}."b" (1 row) -select '$.a.**{5,}.b'::jsonpath; - jsonpath ------------------- - $."a".**{5,}."b" +select '$.a.**{5 to last}.b'::jsonpath; + jsonpath +------------------------- + $."a".**{5 to last}."b" (1 row) select '$+1'::jsonpath; diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 8b36a30049..369463d844 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -67,38 +67,40 @@ select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ != null)'); select * from jsonpath_query(jsonb '[1, "2", null]', '$[*] ? (@ == null)'); select jsonb '{"a": {"b": 1}}' @* 'lax $.**'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; -select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; +select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; +select jsonb '{"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)'; -select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)'; -select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; -select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)'; +select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)'; select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; -select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; +select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)'; select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 32c33f150f..c656165c10 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -17,10 +17,10 @@ select '$[*].a'::jsonpath; select '$[*][0].a.b'::jsonpath; select '$.a.**.b'::jsonpath; select '$.a.**{2}.b'::jsonpath; -select '$.a.**{2,2}.b'::jsonpath; -select '$.a.**{2,5}.b'::jsonpath; -select '$.a.**{,5}.b'::jsonpath; -select '$.a.**{5,}.b'::jsonpath; +select '$.a.**{2 to 2}.b'::jsonpath; +select '$.a.**{2 to 5}.b'::jsonpath; +select '$.a.**{0 to 5}.b'::jsonpath; +select '$.a.**{5 to last}.b'::jsonpath; select '$+1'::jsonpath; select '$-1'::jsonpath; select '$--+1'::jsonpath; From 5cb0ca92b807475fac0d694798d6df41fd0c0ebf Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Mar 2018 22:39:09 +0300 Subject: [PATCH 58/97] Decompose jsonpath strict/lax flag --- src/backend/utils/adt/jsonpath_exec.c | 59 ++++++++++++++++----------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 1fd33f4571..0520699022 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -26,11 +26,18 @@ typedef struct JsonPathExecContext { List *vars; - bool lax; JsonbValue *root; /* for $ evaluation */ int innermostArraySize; /* for LAST array index evaluation */ + bool laxMode; + bool ignoreStructuralErrors; } 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) + typedef struct JsonValueListIterator { ListCell *lcell; @@ -724,7 +731,7 @@ static inline JsonPathExecResult recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - if (cxt->lax) + if (jspAutoUnwrap(cxt)) { JsonValueList seq = { 0 }; JsonValueListIterator it = { 0 }; @@ -816,14 +823,14 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) if (res == jperOk) { - if (cxt->lax) + if (!jspStrictAbsenseOfErrors(cxt)) return jperOk; found = true; } else if (res == jperError) { - if (!cxt->lax) + if (jspStrictAbsenseOfErrors(cxt)) return jperError; error = true; @@ -1153,7 +1160,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (whole->type != jbvString) { - if (!cxt->lax) + if (jspStrictAbsenseOfErrors(cxt)) return jperError; error = true; @@ -1163,7 +1170,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, initial->val.string.val, initial->val.string.len)) { - if (cxt->lax) + if (!jspStrictAbsenseOfErrors(cxt)) return jperOk; found = true; @@ -1220,7 +1227,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (str->type != jbvString) { - if (!cxt->lax) + if (jspStrictAbsenseOfErrors(cxt)) return jperError; error = true; @@ -1229,7 +1236,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, str->val.string.len, cflags, DEFAULT_COLLATION_OID, 0, NULL)) { - if (cxt->lax) + if (!jspStrictAbsenseOfErrors(cxt)) return jperOk; found = true; @@ -1400,13 +1407,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jspHasNext(jsp) || !found) pfree(v); /* free value if it was not added to found list */ } - else if (!cxt->lax) + else if (!jspIgnoreStructuralErrors(cxt)) { Assert(found); res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); } } - else if (!cxt->lax) + else if (!jspIgnoreStructuralErrors(cxt)) { Assert(found); res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); @@ -1483,7 +1490,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } } - else + else if (!jspIgnoreStructuralErrors(cxt)) res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; @@ -1523,7 +1530,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, else index_to = index_from; - if (!cxt->lax && + if (!jspIgnoreStructuralErrors(cxt) && (index_from < 0 || index_from > index_to || index_to >= size)) @@ -1569,8 +1576,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, cxt->innermostArraySize = innermostArraySize; } - else + else if (!jspIgnoreStructuralErrors(cxt)) + { res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); + } break; case jpiLast: @@ -1631,7 +1640,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } } - else if (!cxt->lax) + else if (!jspIgnoreStructuralErrors(cxt)) { Assert(found); res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND); @@ -1693,9 +1702,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiExists: jspGetArg(jsp, &elem); - if (cxt->lax) - res = recursiveExecute(cxt, &elem, jb, NULL); - else + if (jspStrictAbsenseOfErrors(cxt)) { JsonValueList vals = { 0 }; @@ -1708,6 +1715,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!jperIsError(res)) res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; } + else + { + res = recursiveExecute(cxt, &elem, jb, NULL); + } res = appendBoolResult(cxt, jsp, found, res, needBool); break; @@ -1751,9 +1762,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (size < 0) { - if (!cxt->lax) + if (!jspAutoWrap(cxt)) { - res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); + if (!jspIgnoreStructuralErrors(cxt)) + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; } @@ -2120,7 +2132,7 @@ static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - if (cxt->lax && JsonbType(jb) == jbvArray) + if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray) return recursiveExecuteUnwrapArray(cxt, jsp, jb, found); return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false); @@ -2172,7 +2184,7 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - if (cxt->lax) + if (jspAutoUnwrap(cxt)) { switch (jsp->type) { @@ -2247,11 +2259,12 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJso jspInit(&jsp, path); cxt.vars = vars; - cxt.lax = (path->header & JSONPATH_LAX) != 0; + cxt.laxMode = (path->header & JSONPATH_LAX) != 0; + cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = JsonbInitBinary(&jbv, json); cxt.innermostArraySize = -1; - if (!cxt.lax && !foundJson) + if (jspStrictAbsenseOfErrors(&cxt) && !foundJson) { /* * In strict mode we must get a complete list of values to check From aeb8e3e241245ed0df4e5c4a3636d22c71a16abd Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Mar 2018 22:39:39 +0300 Subject: [PATCH 59/97] Fix jsonpath .** in strict mode --- src/backend/utils/adt/jsonpath_exec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0520699022..16aa52641c 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1059,7 +1059,11 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, v.type != jbvBinary)) /* leaves only requested */ { /* check expression */ + bool ignoreStructuralErrors = cxt->ignoreStructuralErrors; + + cxt->ignoreStructuralErrors = true; res = recursiveExecuteNext(cxt, NULL, jsp, &v, found, true); + cxt->ignoreStructuralErrors = ignoreStructuralErrors; if (jperIsError(res)) break; @@ -1683,7 +1687,11 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, /* first try without any intermediate steps */ if (jsp->content.anybounds.first == 0) { + bool ignoreStructuralErrors = cxt->ignoreStructuralErrors; + + cxt->ignoreStructuralErrors = true; res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, true); + cxt->ignoreStructuralErrors = ignoreStructuralErrors; if (res == jperOk && !found) break; From 33a290c9f6fd541aa211bba040ed87cbde8b43ac Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 22 Mar 2018 14:25:59 +0300 Subject: [PATCH 60/97] Refactor boolean jsonpath expressions execution --- src/backend/utils/adt/jsonpath_exec.c | 388 +++++++++++++------------- src/include/utils/jsonpath.h | 8 + 2 files changed, 206 insertions(+), 190 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 16aa52641c..0cbad5e2b2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -49,9 +49,6 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); -static inline JsonPathExecResult recursiveExecuteBool(JsonPathExecContext *cxt, - JsonPathItem *jsp, JsonbValue *jb); - static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); @@ -555,7 +552,7 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, bool *error) return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); } -static inline JsonPathExecResult +static inline JsonPathBool checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) { bool eq = false; @@ -563,9 +560,9 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) if (jb1->type != jb2->type) { if (jb1->type == jbvNull || jb2->type == jbvNull) - return not ? jperOk : jperNotFound; + return not ? jpbTrue : jpbFalse; - return jperError; + return jpbUnknown; } switch (jb1->type) @@ -595,7 +592,7 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) &error) == 0; if (error) - return jperError; + return jpbUnknown; break; } @@ -603,16 +600,16 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) case jbvBinary: case jbvObject: case jbvArray: - return jperError; + return jpbUnknown; default: elog(ERROR, "Unknown jsonb value type %d", jb1->type); } - return (not ^ eq) ? jperOk : jperNotFound; + return (not ^ eq) ? jpbTrue : jpbFalse; } -static JsonPathExecResult +static JsonPathBool makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) { int cmp; @@ -622,11 +619,11 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) { if (jb1->type != jbvNull && jb2->type != jbvNull) /* non-null items of different types are not order-comparable */ - return jperError; + return jpbUnknown; if (jb1->type != jbvNull || jb2->type != jbvNull) /* comparison of nulls to non-nulls returns always false */ - return jperNotFound; + return jpbFalse; /* both values are JSON nulls */ } @@ -655,11 +652,11 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) &error); if (error) - return jperError; + return jpbUnknown; } break; default: - return jperError; + return jpbUnknown; } switch (op) @@ -684,10 +681,10 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) break; default: elog(ERROR, "Unknown operation"); - return jperError; + return jpbUnknown; } - return res ? jperOk : jperNotFound; + return res ? jpbTrue : jpbFalse; } static JsonbValue * @@ -774,8 +771,8 @@ recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, return recursiveExecute(cxt, jsp, jb, found); } -static JsonPathExecResult -executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) +static JsonPathBool +executeComparison(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { JsonPathExecResult res; JsonPathItem elem; @@ -789,12 +786,12 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) jspGetLeftArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); if (jperIsError(res)) - return jperError; + return jpbUnknown; jspGetRightArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); if (jperIsError(res)) - return jperError; + return jpbUnknown; while ((lval = JsonValueListNext(&lseq, &lseqit))) { @@ -803,35 +800,39 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) while ((rval = JsonValueListNext(&rseq, &rseqit))) { + JsonPathBool cmp; + switch (jsp->type) { case jpiEqual: - res = checkEquality(lval, rval, false); + cmp = checkEquality(lval, rval, false); break; case jpiNotEqual: - res = checkEquality(lval, rval, true); + cmp = checkEquality(lval, rval, true); break; case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: - res = makeCompare(jsp->type, lval, rval); + cmp = makeCompare(jsp->type, lval, rval); break; default: elog(ERROR, "Unknown operation"); + cmp = jpbUnknown; + break; } - if (res == jperOk) + if (cmp == jpbTrue) { if (!jspStrictAbsenseOfErrors(cxt)) - return jperOk; + return jpbTrue; found = true; } - else if (res == jperError) + else if (cmp == jpbUnknown) { if (jspStrictAbsenseOfErrors(cxt)) - return jperError; + return jpbUnknown; error = true; } @@ -839,12 +840,12 @@ executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) } if (found) /* possible only in strict mode */ - return jperOk; + return jpbTrue; if (error) /* possible only in lax mode */ - return jperError; + return jpbUnknown; - return jperNotFound; + return jpbFalse; } static JsonPathExecResult @@ -1119,7 +1120,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return jperOk; } -static JsonPathExecResult +static JsonPathBool executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { @@ -1137,10 +1138,10 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetRightArg(jsp, &elem); res = recursiveExecute(cxt, &elem, jb, &rseq); if (jperIsError(res)) - return jperError; + return jpbUnknown; if (JsonValueListLength(&rseq) != 1) - return jperError; + return jpbUnknown; initial = JsonValueListHead(&rseq); @@ -1148,12 +1149,12 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf); if (initial->type != jbvString) - return jperError; + return jpbUnknown; jspGetLeftArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); if (jperIsError(res)) - return jperError; + return jpbUnknown; while ((whole = JsonValueListNext(&lseq, &lit))) { @@ -1165,7 +1166,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (whole->type != jbvString) { if (jspStrictAbsenseOfErrors(cxt)) - return jperError; + return jpbUnknown; error = true; } @@ -1175,22 +1176,22 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, initial->val.string.len)) { if (!jspStrictAbsenseOfErrors(cxt)) - return jperOk; + return jpbTrue; found = true; } } if (found) /* possible only in strict mode */ - return jperOk; + return jpbTrue; if (error) /* possible only in lax mode */ - return jperError; + return jpbUnknown; - return jperNotFound; + return jpbFalse; } -static JsonPathExecResult +static JsonPathBool executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { @@ -1220,7 +1221,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jspInitByBuffer(&elem, jsp->base, jsp->content.like_regex.expr); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); if (jperIsError(res)) - return jperError; + return jpbUnknown; while ((str = JsonValueListNext(&seq, &it))) { @@ -1232,7 +1233,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (str->type != jbvString) { if (jspStrictAbsenseOfErrors(cxt)) - return jperError; + return jpbUnknown; error = true; } @@ -1241,19 +1242,19 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, DEFAULT_COLLATION_OID, 0, NULL)) { if (!jspStrictAbsenseOfErrors(cxt)) - return jperOk; + return jpbTrue; found = true; } } if (found) /* possible only in strict mode */ - return jperOk; + return jpbTrue; if (error) /* possible only in lax mode */ - return jperError; + return jpbUnknown; - return jperNotFound; + return jpbFalse; } /* @@ -1287,34 +1288,140 @@ tryToParseDatetime(const char *fmt, int fmtlen, text *datetime, char *tzname, return ok; } +/* + * Convert boolean execution status 'res' to a boolean JSON item and execute + * next jsonpath. + */ static inline JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonValueList *found, JsonPathExecResult res, bool needBool) + JsonValueList *found, JsonPathBool res) { JsonPathItem next; JsonbValue jbv; bool hasNext = jspGetNext(jsp, &next); - if (needBool) - { - Assert(!hasNext); - return res; - } - if (!found && !hasNext) return jperOk; /* found singleton boolean value */ - if (jperIsError(res)) + if (res == jpbUnknown) + { jbv.type = jbvNull; + } else { jbv.type = jbvBool; - jbv.val.boolean = res == jperOk; + jbv.val.boolean = res == jpbTrue; } return recursiveExecuteNext(cxt, jsp, &next, &jbv, found, true); } +/* Execute boolean-valued jsonpath expression. */ +static inline JsonPathBool +recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, bool canHaveNext) +{ + JsonPathItem arg; + JsonPathBool res; + JsonPathBool res2; + + if (!canHaveNext && jspHasNext(jsp)) + elog(ERROR, "boolean jsonpath item can not have next item"); + + switch (jsp->type) + { + case jpiAnd: + jspGetLeftArg(jsp, &arg); + res = recursiveExecuteBool(cxt, &arg, jb, false); + + if (res == jpbFalse) + return jpbFalse; + + /* + * SQL/JSON says that we should check second arg + * in case of jperError + */ + + jspGetRightArg(jsp, &arg); + res2 = recursiveExecuteBool(cxt, &arg, jb, false); + + return res2 == jpbTrue ? res : res2; + + case jpiOr: + jspGetLeftArg(jsp, &arg); + res = recursiveExecuteBool(cxt, &arg, jb, false); + + if (res == jpbTrue) + return jpbTrue; + + jspGetRightArg(jsp, &arg); + res2 = recursiveExecuteBool(cxt, &arg, jb, false); + + return res2 == jpbFalse ? res : res2; + + case jpiNot: + jspGetArg(jsp, &arg); + + res = recursiveExecuteBool(cxt, &arg, jb, false); + + if (res == jpbUnknown) + return jpbUnknown; + + return res == jpbTrue ? jpbFalse : jpbTrue; + + case jpiIsUnknown: + jspGetArg(jsp, &arg); + res = recursiveExecuteBool(cxt, &arg, jb, false); + return res == jpbUnknown ? jpbTrue : jpbFalse; + + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + return executeComparison(cxt, jsp, jb); + + case jpiStartsWith: + return executeStartsWithPredicate(cxt, jsp, jb); + + case jpiLikeRegex: + return executeLikeRegexPredicate(cxt, jsp, jb); + + case jpiExists: + jspGetArg(jsp, &arg); + + if (jspStrictAbsenseOfErrors(cxt)) + { + /* + * In strict mode we must get a complete list of values + * to check that there are no errors at all. + */ + JsonValueList vals = { 0 }; + JsonPathExecResult res = + recursiveExecute(cxt, &arg, jb, &vals); + + if (jperIsError(res)) + return jpbUnknown; + + return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue; + } + else + { + JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL); + + if (jperIsError(res)) + return jpbUnknown; + + return res == jperOk ? jpbTrue : jpbFalse; + } + + default: + elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type); + return jpbUnknown; + } +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1327,7 +1434,7 @@ appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp, */ static JsonPathExecResult recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb, JsonValueList *found, bool needBool) + JsonbValue *jb, JsonValueList *found) { JsonPathItem elem; JsonPathExecResult res = jperNotFound; @@ -1335,61 +1442,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, check_stack_depth(); - switch(jsp->type) { + switch (jsp->type) + { + /* all boolean item types: */ case jpiAnd: - jspGetLeftArg(jsp, &elem); - res = recursiveExecuteBool(cxt, &elem, jb); - if (res != jperNotFound) - { - JsonPathExecResult res2; - - /* - * SQL/JSON says that we should check second arg - * in case of jperError - */ - - jspGetRightArg(jsp, &elem); - res2 = recursiveExecuteBool(cxt, &elem, jb); - - res = (res2 == jperOk) ? res : res2; - } - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; case jpiOr: - jspGetLeftArg(jsp, &elem); - res = recursiveExecuteBool(cxt, &elem, jb); - if (res != jperOk) - { - JsonPathExecResult res2; - - jspGetRightArg(jsp, &elem); - res2 = recursiveExecuteBool(cxt, &elem, jb); - - res = (res2 == jperNotFound) ? res : res2; - } - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; case jpiNot: - jspGetArg(jsp, &elem); - switch ((res = recursiveExecuteBool(cxt, &elem, jb))) + case jpiIsUnknown: + case jpiEqual: + case jpiNotEqual: + case jpiLess: + case jpiGreater: + case jpiLessOrEqual: + case jpiGreaterOrEqual: + case jpiExists: + case jpiStartsWith: + case jpiLikeRegex: { - case jperOk: - res = jperNotFound; - break; - case jperNotFound: - res = jperOk; - break; - default: - break; + JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true); + + res = appendBoolResult(cxt, jsp, found, st); + break; } - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; - case jpiIsUnknown: - jspGetArg(jsp, &elem); - res = recursiveExecuteBool(cxt, &elem, jb); - res = jperIsError(res) ? jperOk : jperNotFound; - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; + case jpiKey: if (JsonbType(jb) == jbvObject) { @@ -1650,15 +1725,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND); } break; - case jpiEqual: - case jpiNotEqual: - case jpiLess: - case jpiGreater: - case jpiLessOrEqual: - case jpiGreaterOrEqual: - res = executeExpr(cxt, jsp, jb); - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; case jpiAdd: case jpiSub: case jpiMul: @@ -1671,13 +1737,17 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = executeUnaryArithmExpr(cxt, jsp, jb, found); break; case jpiFilter: - jspGetArg(jsp, &elem); - res = recursiveExecuteBool(cxt, &elem, jb); - if (res != jperOk) - res = jperNotFound; - else - res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); - break; + { + JsonPathBool st; + + jspGetArg(jsp, &elem); + st = recursiveExecuteBool(cxt, &elem, jb, false); + if (st != jpbTrue) + res = jperNotFound; + else + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); + break; + } case jpiAny: { JsonbValue jbvbuf; @@ -1707,29 +1777,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jsp->content.anybounds.last); break; } - case jpiExists: - jspGetArg(jsp, &elem); - - if (jspStrictAbsenseOfErrors(cxt)) - { - JsonValueList vals = { 0 }; - - /* - * In strict mode we must get a complete list of values - * to check that there are no errors at all. - */ - res = recursiveExecute(cxt, &elem, jb, &vals); - - if (!jperIsError(res)) - res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk; - } - else - { - res = recursiveExecute(cxt, &elem, jb, NULL); - } - - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; case jpiNull: case jpiBool: case jpiNumeric: @@ -1923,7 +1970,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetRightArg(jsp, &elem); tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb, - &tzlist, false); + &tzlist); if (jperIsError(tzres)) return tzres; @@ -2076,14 +2123,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } break; - case jpiStartsWith: - res = executeStartsWithPredicate(cxt, jsp, jb); - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; - case jpiLikeRegex: - res = executeLikeRegexPredicate(cxt, jsp, jb); - res = appendBoolResult(cxt, jsp, found, res, needBool); - break; default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } @@ -2104,7 +2143,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, for (; elem < last; elem++) { - res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false); + res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found); if (jperIsError(res)) break; @@ -2124,7 +2163,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, { if (tok == WJB_ELEM) { - res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found, false); + res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found); if (jperIsError(res)) break; if (res == jperOk && !found) @@ -2143,7 +2182,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray) return recursiveExecuteUnwrapArray(cxt, jsp, jb, found); - return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false); + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); } /* @@ -2219,40 +2258,9 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } - return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false); + return recursiveExecuteNoUnwrap(cxt, jsp, jb, found); } -static inline JsonPathExecResult -recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, - JsonbValue *jb) -{ - if (jspHasNext(jsp)) - elog(ERROR, "boolean jsonpath item can not have next item"); - - switch (jsp->type) - { - case jpiAnd: - case jpiOr: - case jpiNot: - case jpiIsUnknown: - case jpiEqual: - case jpiNotEqual: - case jpiGreater: - case jpiGreaterOrEqual: - case jpiLess: - case jpiLessOrEqual: - case jpiExists: - case jpiStartsWith: - case jpiLikeRegex: - break; - - default: - elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type); - break; - } - - return recursiveExecuteNoUnwrap(cxt, jsp, jb, NULL, true); -} /* * Public interface to jsonpath executor diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 6f56e85dbe..5ebd12aad1 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -229,6 +229,14 @@ extern JsonPathParseResult* parsejsonpath(const char *str, int len); * Evaluation of jsonpath */ +/* Result of jsonpath predicate evaluation */ +typedef enum JsonPathBool +{ + jpbFalse = 0, + jpbTrue = 1, + jpbUnknown = 2 +} JsonPathBool; + typedef enum JsonPathExecStatus { jperOk = 0, From d680b249b52b24c2e2feaf2a74e2934f357bf73e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 23 Mar 2018 17:45:59 +0300 Subject: [PATCH 61/97] Add extended jsonpath errors --- src/backend/utils/adt/jsonpath_exec.c | 57 ++++++++++++-------- src/include/utils/jsonpath.h | 39 +++++++++----- src/test/regress/expected/jsonb_jsonpath.out | 2 +- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 0cbad5e2b2..2e694b6995 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -23,6 +23,10 @@ #include "utils/jsonpath.h" #include "utils/varlena.h" +/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */ +ErrorData jperNotFound[1]; + + typedef struct JsonPathExecContext { List *vars; @@ -786,12 +790,12 @@ executeComparison(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) jspGetLeftArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); jspGetRightArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); while ((lval = JsonValueListNext(&lseq, &lseqit))) { @@ -937,14 +941,16 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, PG_CATCH(); { int errcode = geterrcode(); + ErrorData *edata; if (ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION) PG_RE_THROW(); - FlushErrorState(); MemoryContextSwitchTo(mcxt); + edata = CopyErrorData(); + FlushErrorState(); - return jperMakeError(errcode); + return jperMakeErrorData(edata); } PG_END_TRY(); @@ -971,7 +977,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); if (jperIsError(jper)) - return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND); + return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND)); jper = jperNotFound; @@ -1138,7 +1144,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetRightArg(jsp, &elem); res = recursiveExecute(cxt, &elem, jb, &rseq); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); if (JsonValueListLength(&rseq) != 1) return jpbUnknown; @@ -1154,7 +1160,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetLeftArg(jsp, &elem); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); while ((whole = JsonValueListNext(&lseq, &lit))) { @@ -1221,7 +1227,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, jspInitByBuffer(&elem, jsp->base, jsp->content.like_regex.expr); res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); while ((str = JsonValueListNext(&seq, &it))) { @@ -1402,7 +1408,7 @@ recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, recursiveExecute(cxt, &arg, jb, &vals); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue; } @@ -1411,7 +1417,7 @@ recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL); if (jperIsError(res)) - return jpbUnknown; + return jperReplace(res, jpbUnknown); return res == jperOk ? jpbTrue : jpbFalse; } @@ -2381,59 +2387,65 @@ makePassingVars(Jsonb *jb) static void throwJsonPathError(JsonPathExecResult res) { + int err; if (!jperIsError(res)) return; - switch (jperGetError(res)) + if (jperIsErrorData(res)) + ThrowErrorData(jperGetErrorData(res)); + + err = jperGetError(res); + + switch (err) { case ERRCODE_JSON_ARRAY_NOT_FOUND: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("SQL/JSON array not found"))); break; case ERRCODE_JSON_OBJECT_NOT_FOUND: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("SQL/JSON object not found"))); break; case ERRCODE_JSON_MEMBER_NOT_FOUND: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("SQL/JSON member not found"))); break; case ERRCODE_JSON_NUMBER_NOT_FOUND: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("SQL/JSON number not found"))); break; case ERRCODE_JSON_SCALAR_REQUIRED: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("SQL/JSON scalar required"))); break; case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("Singleton SQL/JSON item required"))); break; case ERRCODE_NON_NUMERIC_JSON_ITEM: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("Non-numeric SQL/JSON item"))); break; case ERRCODE_INVALID_JSON_SUBSCRIPT: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("Invalid SQL/JSON subscript"))); break; case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("Invalid argument for SQL/JSON datetime function"))); break; default: ereport(ERROR, - (errcode(jperGetError(res)), + (errcode(err), errmsg("Unknown SQL/JSON error"))); break; } @@ -2456,7 +2468,10 @@ jsonb_jsonpath_exists(PG_FUNCTION_ARGS) PG_FREE_IF_COPY(jp, 1); if (jperIsError(res)) + { + jperFree(res); PG_RETURN_NULL(); + } PG_RETURN_BOOL(res == jperOk); } diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 5ebd12aad1..73e3f317f0 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -237,21 +237,32 @@ typedef enum JsonPathBool jpbUnknown = 2 } JsonPathBool; -typedef enum JsonPathExecStatus +/* Result of jsonpath evaluation */ +typedef ErrorData *JsonPathExecResult; + +/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */ +extern ErrorData jperNotFound[1]; + +#define jperOk NULL +#define jperIsError(jper) ((jper) && (jper)->sqlerrcode) +#define jperIsErrorData(jper) ((jper) && (jper)->elevel > 0) +#define jperGetError(jper) ((jper)->sqlerrcode) +#define jperMakeErrorData(edata) (edata) +#define jperGetErrorData(jper) (jper) +#define jperFree(jper) ((jper) && (jper)->sqlerrcode ? \ + (jper)->elevel > 0 ? FreeErrorData(jper) : pfree(jper) : (void) 0) +#define jperReplace(jper1, jper2) (jperFree(jper1), (jper2)) + +/* Returns special SQL/JSON ErrorData with zero elevel */ +static inline JsonPathExecResult +jperMakeError(int sqlerrcode) { - jperOk = 0, - jperError, - jperFatalError, - jperNotFound -} JsonPathExecStatus; - -typedef uint64 JsonPathExecResult; - -#define jperStatus(jper) ((JsonPathExecStatus)(uint32)(jper)) -#define jperIsError(jper) (jperStatus(jper) == jperError) -#define jperGetError(jper) ((uint32)((jper) >> 32)) -#define jperMakeError(err) (((uint64)(err) << 32) | jperError) -#define jperFree(jper) ((void) 0) + ErrorData *edata = palloc0(sizeof(*edata)); + + edata->sqlerrcode = sqlerrcode; + + return edata; +} typedef Datum (*JsonPathVariable_cb)(void *, bool *); diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 8a30e9c763..22f7255b5c 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -807,7 +807,7 @@ select jsonb '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)'; (1 row) select jsonb '0' @* '1 / $'; -ERROR: Unknown SQL/JSON error +ERROR: division by zero -- unwrapping of operator arguments in lax mode select jsonb '{"a": [2]}' @* 'lax $.a * 3'; ?column? From 08294cb98c4346db14a1d335dcaab737d263ad7c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 19 Apr 2018 01:53:11 +0300 Subject: [PATCH 62/97] Fix jsonpath escaping --- src/backend/utils/adt/jsonpath_scan.l | 236 ++++++++++++++++--------- src/test/regress/expected/jsonpath.out | 30 ++++ src/test/regress/sql/jsonpath.sql | 6 + 3 files changed, 187 insertions(+), 85 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index aad4aa2635..8101ffb265 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -32,6 +32,7 @@ static void addchar(bool init, char s); static int checkSpecialVal(void); /* examine scanstring for the special value */ static void parseUnicode(char *s, int l); +static void parseHexChars(char *s, int l); /* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ #undef fprintf @@ -62,12 +63,16 @@ fprintf_to_ereport(const char *fmt, const char *msg) %x xQUOTED %x xNONQUOTED %x xVARQUOTED +%x xSINGLEQUOTED %x xCOMMENT special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/] -any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f] +any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f] blank [ \t\n\r\f] -unicode \\u[0-9A-Fa-f]{4} +hex_dig [0-9A-Fa-f] +unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\}) +hex_char \\x{hex_dig}{2} + %% @@ -152,6 +157,11 @@ unicode \\u[0-9A-Fa-f]{4} BEGIN xQUOTED; } +\' { + addchar(true, '\0'); + BEGIN xSINGLEQUOTED; + } + \\ { yyless(0); addchar(true, '\0'); @@ -174,7 +184,7 @@ unicode \\u[0-9A-Fa-f]{4} BEGIN xCOMMENT; } -({special}|\") { +({special}|\"|\') { yylval->str = scanstring; yyless(0); BEGIN INITIAL; @@ -187,41 +197,56 @@ unicode \\u[0-9A-Fa-f]{4} return checkSpecialVal(); } -\\[\"\\] { addchar(false, yytext[1]); } +\\[\"\'\\] { addchar(false, yytext[1]); } + +\\b { addchar(false, '\b'); } + +\\f { addchar(false, '\f'); } -\\b { addchar(false, '\b'); } +\\n { addchar(false, '\n'); } -\\f { addchar(false, '\f'); } +\\r { addchar(false, '\r'); } -\\n { addchar(false, '\n'); } +\\t { addchar(false, '\t'); } -\\r { addchar(false, '\r'); } +\\v { addchar(false, '\v'); } -\\t { addchar(false, '\t'); } +{unicode}+ { parseUnicode(yytext, yyleng); } -{unicode}+ { parseUnicode(yytext, yyleng); } +{hex_char}+ { parseHexChars(yytext, yyleng); } -\\u { yyerror(NULL, "Unicode sequence is invalid"); } +\\x { yyerror(NULL, "Hex character sequence is invalid"); } -\\. { yyerror(NULL, "Escape sequence is invalid"); } +\\u { yyerror(NULL, "Unicode sequence is invalid"); } -\\ { yyerror(NULL, "Unexpected end after backslash"); } +\\. { yyerror(NULL, "Escape sequence is invalid"); } -<> { yyerror(NULL, "Unexpected end of quoted string"); } +\\ { yyerror(NULL, "Unexpected end after backslash"); } + +<> { yyerror(NULL, "Unexpected end of quoted string"); } \" { yylval->str = scanstring; BEGIN INITIAL; return STRING_P; } -\" { + +\" { yylval->str = scanstring; BEGIN INITIAL; return VARIABLE_P; } +\' { + yylval->str = scanstring; + BEGIN INITIAL; + return STRING_P; + } + [^\\\"]+ { addstring(false, yytext, yyleng); } +[^\\\']+ { addstring(false, yytext, yyleng); } + <> { yyterminate(); } \*\/ { BEGIN INITIAL; } @@ -436,6 +461,85 @@ hexval(char c) return 0; /* not reached */ } +static void +addUnicodeChar(int ch) +{ + /* + * For UTF8, replace the escape sequence by the actual + * utf8 character in lex->strval. Do this also for other + * encodings if the escape designates an ASCII character, + * otherwise raise an error. + */ + + if (ch == 0) + { + /* We can't allow this, since our TEXT type doesn't */ + ereport(ERROR, + (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), + errmsg("unsupported Unicode escape sequence"), + errdetail("\\u0000 cannot be converted to text."))); + } + else if (GetDatabaseEncoding() == PG_UTF8) + { + char utf8str[5]; + int utf8len; + + unicode_to_utf8(ch, (unsigned char *) utf8str); + utf8len = pg_utf_mblen((unsigned char *) utf8str); + addstring(false, utf8str, utf8len); + } + else if (ch <= 0x007f) + { + /* + * This is the only way to designate things like a + * form feed character in JSON, so it's useful in all + * encodings. + */ + addchar(false, (char) ch); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8."))); + } +} + +static void +addUnicode(int ch, int *hi_surrogate) +{ + if (ch >= 0xd800 && ch <= 0xdbff) + { + if (*hi_surrogate != -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode high surrogate must not follow a high surrogate."))); + *hi_surrogate = (ch & 0x3ff) << 10; + return; + } + else if (ch >= 0xdc00 && ch <= 0xdfff) + { + if (*hi_surrogate == -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode low surrogate must follow a high surrogate."))); + ch = 0x10000 + *hi_surrogate + (ch & 0x3ff); + *hi_surrogate = -1; + } + else if (*hi_surrogate != -1) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode low surrogate must follow a high surrogate."))); + } + + addUnicodeChar(ch); +} + /* * parseUnicode was adopted from json_lex_string() in * src/backend/utils/adt/json.c @@ -443,88 +547,50 @@ hexval(char c) static void parseUnicode(char *s, int l) { - int i, j; - int ch = 0; - int hi_surrogate = -1; - - Assert(l % 6 /* \uXXXX */ == 0); + int i; + int hi_surrogate = -1; - for(i = 0; i < l / 6; i++) + for (i = 2; i < l; i += 2) /* skip '\u' */ { - ch = 0; - - for(j=0; j<4; j++) - ch = (ch << 4) | hexval(s[ i*6 + 2 + j]); + int ch = 0; + int j; - if (ch >= 0xd800 && ch <= 0xdbff) + if (s[i] == '{') /* parse '\u{XX...}' */ { - if (hi_surrogate != -1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type jsonpath"), - errdetail("Unicode high surrogate must not follow a high surrogate."))); - hi_surrogate = (ch & 0x3ff) << 10; - continue; + while (s[++i] != '}' && i < l) + ch = (ch << 4) | hexval(s[i]); + i++; /* ski p '}' */ } - else if (ch >= 0xdc00 && ch <= 0xdfff) + else /* parse '\uXXXX' */ { - if (hi_surrogate == -1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type jsonpath"), - errdetail("Unicode low surrogate must follow a high surrogate."))); - ch = 0x10000 + hi_surrogate + (ch & 0x3ff); - hi_surrogate = -1; + for (j = 0; j < 4 && i < l; j++) + ch = (ch << 4) | hexval(s[i++]); } - if (hi_surrogate != -1) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type jsonpath"), - errdetail("Unicode low surrogate must follow a high surrogate."))); + addUnicode(ch, &hi_surrogate); + } - /* - * For UTF8, replace the escape sequence by the actual - * utf8 character in lex->strval. Do this also for other - * encodings if the escape designates an ASCII character, - * otherwise raise an error. - */ + if (hi_surrogate != -1) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type jsonpath"), + errdetail("Unicode low surrogate must follow a high surrogate."))); + } +} - if (ch == 0) - { - /* We can't allow this, since our TEXT type doesn't */ - ereport(ERROR, - (errcode(ERRCODE_UNTRANSLATABLE_CHARACTER), - errmsg("unsupported Unicode escape sequence"), - errdetail("\\u0000 cannot be converted to text."))); - } - else if (GetDatabaseEncoding() == PG_UTF8) - { - char utf8str[5]; - int utf8len; +static void +parseHexChars(char *s, int l) +{ + int i; - unicode_to_utf8(ch, (unsigned char *) utf8str); - utf8len = pg_utf_mblen((unsigned char *) utf8str); - addstring(false, utf8str, utf8len); - } - else if (ch <= 0x007f) - { - /* - * This is the only way to designate things like a - * form feed character in JSON, so it's useful in all - * encodings. - */ - addchar(false, (char) ch); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type jsonpath"), - errdetail("Unicode escape values cannot be used for code point values above 007F when the server encoding is not UTF8."))); - } + Assert(l % 4 /* \xXX */ == 0); + + for (i = 0; i < l / 4; i++) + { + int ch = (hexval(s[i * 4 + 2]) << 4) | hexval(s[i * 4 + 3]); - hi_surrogate = -1; + addUnicodeChar(ch); } } diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 781e3f4344..8cd0534195 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -147,6 +147,36 @@ select '$.a/+-1'::jsonpath; ($."a" / -1) (1 row) +select '"\b\f\r\n\t\v\"\''\\"'::jsonpath; + jsonpath +------------------------- + "\b\f\r\n\t\u000b\"'\\" +(1 row) + +select '''\b\f\r\n\t\v\"\''\\'''::jsonpath; + jsonpath +------------------------- + "\b\f\r\n\t\u000b\"'\\" +(1 row) + +select '"\x50\u0067\u{53}\u{051}\u{00004C}"'::jsonpath; + jsonpath +---------- + "PgSQL" +(1 row) + +select '''\x50\u0067\u{53}\u{051}\u{00004C}'''::jsonpath; + jsonpath +---------- + "PgSQL" +(1 row) + +select '$.foo\x50\u0067\u{53}\u{051}\u{00004C}\t\"bar'::jsonpath; + jsonpath +--------------------- + $."fooPgSQL\t\"bar" +(1 row) + select '$.g ? ($.a == 1)'::jsonpath; jsonpath -------------------- diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index c656165c10..7b546873dd 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -26,6 +26,12 @@ select '$-1'::jsonpath; select '$--+1'::jsonpath; select '$.a/+-1'::jsonpath; +select '"\b\f\r\n\t\v\"\''\\"'::jsonpath; +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 '$.g ? ($.a == 1)'::jsonpath; select '$.g ? (@ == 1)'::jsonpath; select '$.g ? (.a == 1)'::jsonpath; From e3b5393ab79239568eb5d21092630f2c2fd28753 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 29 Dec 2017 20:57:08 +0300 Subject: [PATCH 63/97] Add some jsonpath comments --- src/backend/utils/adt/jsonpath_exec.c | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 2e694b6995..0de0f12ab1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -132,6 +132,9 @@ JsonValueListGetList(JsonValueList *jvl) return jvl->list; } +/* + * Get the next item from the sequence advancing iterator. + */ static inline JsonbValue * JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) { @@ -160,6 +163,9 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) return lfirst(it->lcell); } +/* + * Initialize a binary JsonbValue with the given jsonb container. + */ static inline JsonbValue * JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) { @@ -170,6 +176,10 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } +/* + * Transform a JsonbValue into a binary JsonbValue by encoding it to a + * binary jsonb container. + */ static inline JsonbValue * JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out) { @@ -358,6 +368,9 @@ JsonbType(JsonbValue *jb) return type; } +/* + * Get the type name of a SQL/JSON item. + */ static const char * JsonbTypeName(JsonbValue *jb) { @@ -415,6 +428,9 @@ JsonbTypeName(JsonbValue *jb) } } +/* + * Returns the size of an array item, or -1 if item is not an array. + */ static int JsonbArraySize(JsonbValue *jb) { @@ -432,6 +448,9 @@ JsonbArraySize(JsonbValue *jb) return -1; } +/* + * Compare two numerics. + */ static int compareNumeric(Numeric a, Numeric b) { @@ -444,6 +463,10 @@ compareNumeric(Numeric a, Numeric b) ); } +/* + * Cross-type comparison of two datetime SQL/JSON items. If items are + * uncomparable, 'error' flag is set. + */ static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, bool *error) { @@ -556,6 +579,9 @@ compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2, bool *error) return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2)); } +/* + * Check equality of two SLQ/JSON items of the same type. + */ static inline JsonPathBool checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) { @@ -613,6 +639,9 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) return (not ^ eq) ? jpbTrue : jpbFalse; } +/* + * Compare two SLQ/JSON items using comparison operation 'op'. + */ static JsonPathBool makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) { @@ -701,6 +730,9 @@ copyJsonbValue(JsonbValue *src) return dst; } +/* + * Execute next jsonpath item if it does exist. + */ static inline JsonPathExecResult recursiveExecuteNext(JsonPathExecContext *cxt, JsonPathItem *cur, JsonPathItem *next, @@ -728,6 +760,10 @@ recursiveExecuteNext(JsonPathExecContext *cxt, return jperOk; } +/* + * Execute jsonpath expression and automatically unwrap each array item from + * the resulting sequence in lax mode. + */ static inline JsonPathExecResult recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) @@ -775,6 +811,12 @@ recursiveExecuteAndUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, return recursiveExecute(cxt, jsp, jb, found); } +/* + * Execute comparison expression. True is returned only if found any pair of + * items from the left and right operand's sequences which is satisfying + * condition. In strict mode all pairs should be comparable, otherwise an error + * is returned. + */ static JsonPathBool executeComparison(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { @@ -852,6 +894,10 @@ executeComparison(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) return jpbFalse; } +/* + * Execute binary arithemitc expression on singleton numeric operands. + * Array operands are automatically unwrapped in lax mode. + */ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) @@ -961,6 +1007,10 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false); } +/* + * Execute unary arithmetic expression for each numeric item in its operand's + * sequence. Array operand is automatically unwrapped in lax mode. + */ static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) @@ -1095,6 +1145,10 @@ recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return res; } +/* + * Execute array subscript expression and convert resulting numeric item to the + * integer type with truncation. + */ static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, int32 *index) @@ -2136,6 +2190,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, return res; } +/* + * Unwrap current array item and execute jsonpath for each of its elements. + */ static JsonPathExecResult recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) @@ -2181,6 +2238,9 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp, return res; } +/* + * Execute jsonpath with unwrapping of current item if it is an array. + */ static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) @@ -2233,6 +2293,9 @@ wrapItem(JsonbValue *jbv) return JsonbWrapInBinary(jbv, NULL); } +/* + * Execute jsonpath with automatic unwrapping of current item in lax mode. + */ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) From 1789247b6c5f3f942386819d64620d7eaf3850d1 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 24 May 2017 01:59:35 +0300 Subject: [PATCH 64/97] Add jsonpath @? support to GIN json_ops and jsonb_path_ops --- src/backend/utils/adt/jsonb_gin.c | 765 ++++++++++++++++++++--- src/include/catalog/pg_amop.dat | 12 + src/include/utils/jsonb.h | 3 + src/include/utils/jsonpath.h | 2 + src/test/regress/expected/jsonb.out | 453 ++++++++++++++ src/test/regress/expected/opr_sanity.out | 4 +- src/test/regress/sql/jsonb.sql | 79 +++ 7 files changed, 1242 insertions(+), 76 deletions(-) diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c index c8a27451d2..c11960c03b 100644 --- a/src/backend/utils/adt/jsonb_gin.c +++ b/src/backend/utils/adt/jsonb_gin.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "miscadmin.h" #include "access/gin.h" #include "access/hash.h" #include "access/stratnum.h" @@ -20,6 +21,7 @@ #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/jsonb.h" +#include "utils/jsonpath.h" #include "utils/varlena.h" typedef struct PathHashStack @@ -28,9 +30,140 @@ typedef struct PathHashStack struct PathHashStack *parent; } PathHashStack; +typedef enum { eOr, eAnd, eEntry } JsonPathNodeType; + +typedef struct JsonPathNode +{ + JsonPathNodeType type; + union + { + int nargs; + int entryIndex; + Datum entryDatum; + } val; + struct JsonPathNode *args[FLEXIBLE_ARRAY_MEMBER]; +} JsonPathNode; + +typedef struct GinEntries +{ + Datum *buf; + int count; + int allocated; +} GinEntries; + +typedef struct ExtractedPathEntry +{ + struct ExtractedPathEntry *parent; + Datum entry; + JsonPathItemType type; +} ExtractedPathEntry; + +typedef union ExtractedJsonPath +{ + ExtractedPathEntry *entries; + uint32 hash; +} ExtractedJsonPath; + +typedef struct JsonPathExtractionContext +{ + ExtractedJsonPath (*addKey)(ExtractedJsonPath path, char *key, int len); + JsonPath *indexedPaths; + bool pathOps; + bool lax; +} JsonPathExtractionContext; + + static Datum make_text_key(char flag, const char *str, int len); static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key); +static JsonPathNode *gin_extract_jsonpath_expr(JsonPathExtractionContext *cxt, + JsonPathItem *jsp, ExtractedJsonPath path, bool not); + + +static void +gin_entries_init(GinEntries *list, int preallocated) +{ + list->allocated = preallocated; + list->buf = (Datum *) palloc(sizeof(Datum) * list->allocated); + list->count = 0; +} + +static int +gin_entries_add(GinEntries *list, Datum entry) +{ + int id = list->count; + + if (list->count >= list->allocated) + { + + if (list->allocated) + { + list->allocated *= 2; + list->buf = (Datum *) repalloc(list->buf, + sizeof(Datum) * list->allocated); + } + else + { + list->allocated = 8; + list->buf = (Datum *) palloc(sizeof(Datum) * list->allocated); + } + } + + list->buf[list->count++] = entry; + + return id; +} + +/* Append key name to a path. */ +static ExtractedJsonPath +gin_jsonb_ops_add_key(ExtractedJsonPath path, char *key, int len) +{ + ExtractedPathEntry *pentry = palloc(sizeof(*pentry)); + + pentry->parent = path.entries; + + if (key) + { + pentry->entry = make_text_key(JGINFLAG_KEY, key, len); + pentry->type = jpiKey; + } + else + { + pentry->entry = PointerGetDatum(NULL); + pentry->type = len; + } + + path.entries = pentry; + + return path; +} + +/* Combine existing path hash with next key hash. */ +static ExtractedJsonPath +gin_jsonb_path_ops_add_key(ExtractedJsonPath path, char *key, int len) +{ + if (key) + { + JsonbValue jbv; + + jbv.type = jbvString; + jbv.val.string.val = key; + jbv.val.string.len = len; + + JsonbHashScalarValue(&jbv, &path.hash); + } + + return path; +} + +static void +gin_jsonpath_init_context(JsonPathExtractionContext *cxt, bool pathOps, bool lax) +{ + cxt->addKey = pathOps ? gin_jsonb_path_ops_add_key : gin_jsonb_ops_add_key; + cxt->pathOps = pathOps; + cxt->lax = lax; +} + /* * * jsonb_ops GIN opclass support functions @@ -68,12 +201,11 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) { Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); - int total = 2 * JB_ROOT_COUNT(jb); + int total = JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; - int i = 0; - Datum *entries; + GinEntries entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) @@ -83,30 +215,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) } /* Otherwise, use 2 * root count as initial estimate of result size */ - entries = (Datum *) palloc(sizeof(Datum) * total); + gin_entries_init(&entries, 2 * total); it = JsonbIteratorInit(&jb->root); while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) { - /* Since we recurse into the object, we might need more space */ - if (i >= total) - { - total *= 2; - entries = (Datum *) repalloc(entries, sizeof(Datum) * total); - } - switch (r) { case WJB_KEY: - entries[i++] = make_scalar_key(&v, true); + gin_entries_add(&entries, make_scalar_key(&v, true)); break; case WJB_ELEM: /* Pretend string array elements are keys, see jsonb.h */ - entries[i++] = make_scalar_key(&v, (v.type == jbvString)); + gin_entries_add(&entries, make_scalar_key(&v, v.type == jbvString)); break; case WJB_VALUE: - entries[i++] = make_scalar_key(&v, false); + gin_entries_add(&entries, make_scalar_key(&v, false)); break; default: /* we can ignore structural items */ @@ -114,9 +239,447 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) } } - *nentries = i; + *nentries = entries.count; - PG_RETURN_POINTER(entries); + PG_RETURN_POINTER(entries.buf); +} + + +/* + * Extract JSON path into the 'path' with filters. + * Returns true iff this path is supported by the index opclass. + */ +static bool +gin_extract_jsonpath_path(JsonPathExtractionContext *cxt, JsonPathItem *jsp, + ExtractedJsonPath *path, List **filters) +{ + JsonPathItem next; + + for (;;) + { + switch (jsp->type) + { + case jpiRoot: + path->entries = NULL; /* reset path */ + break; + + case jpiCurrent: + break; + + case jpiKey: + { + int keylen; + char *key = jspGetString(jsp, &keylen); + + *path = cxt->addKey(*path, key, keylen); + break; + } + + case jpiIndexArray: + case jpiAnyArray: + *path = cxt->addKey(*path, NULL, jsp->type); + break; + + case jpiAny: + case jpiAnyKey: + if (cxt->pathOps) + /* jsonb_path_ops doesn't support wildcard paths */ + return false; + + *path = cxt->addKey(*path, NULL, jsp->type); + break; + + case jpiFilter: + { + JsonPathItem arg; + JsonPathNode *filter; + + jspGetArg(jsp, &arg); + + filter = gin_extract_jsonpath_expr(cxt, &arg, *path, false); + + if (filter) + *filters = lappend(*filters, filter); + + break; + } + + default: + /* other path items (like item methods) are not supported */ + return false; + } + + if (!jspGetNext(jsp, &next)) + break; + + jsp = &next; + } + + return true; +} + +/* Append an entry node to the global entry list. */ +static inline JsonPathNode * +gin_jsonpath_make_entry_node(Datum entry) +{ + JsonPathNode *node = palloc(offsetof(JsonPathNode, args)); + + node->type = eEntry; + node->val.entryDatum = entry; + + return node; +} + +static inline JsonPathNode * +gin_jsonpath_make_entry_node_scalar(JsonbValue *scalar, bool iskey) +{ + return gin_jsonpath_make_entry_node(make_scalar_key(scalar, iskey)); +} + +static inline JsonPathNode * +gin_jsonpath_make_expr_node(JsonPathNodeType type, int nargs) +{ + JsonPathNode *node = palloc(offsetof(JsonPathNode, args) + + sizeof(node->args[0]) * nargs); + + node->type = type; + node->val.nargs = nargs; + + return node; +} + +static inline JsonPathNode * +gin_jsonpath_make_expr_node_args(JsonPathNodeType type, List *args) +{ + JsonPathNode *node = gin_jsonpath_make_expr_node(type, list_length(args)); + ListCell *lc; + int i = 0; + + foreach(lc, args) + node->args[i++] = lfirst(lc); + + return node; +} + +static inline JsonPathNode * +gin_jsonpath_make_expr_node_binary(JsonPathNodeType type, + JsonPathNode *arg1, JsonPathNode *arg2) +{ + JsonPathNode *node = gin_jsonpath_make_expr_node(type, 2); + + node->args[0] = arg1; + node->args[1] = arg2; + + return node; +} + +/* + * Extract node from the EXISTS/equality-comparison jsonpath expression. If + * 'scalar' is not NULL this is equality-comparsion, otherwise this is + * EXISTS-predicate. The current path is passed in 'pathcxt'. + */ +static JsonPathNode * +gin_extract_jsonpath_node(JsonPathExtractionContext *cxt, JsonPathItem *jsp, + ExtractedJsonPath path, JsonbValue *scalar) +{ + List *nodes = NIL; /* nodes to be AND-ed */ + + /* filters extracted into 'nodes' */ + if (!gin_extract_jsonpath_path(cxt, jsp, &path, &nodes)) + return NULL; + + if (cxt->pathOps) + { + if (scalar) + { + /* append path hash node for equality queries */ + uint32 hash = path.hash; + JsonPathNode *node; + + JsonbHashScalarValue(scalar, &hash); + + node = gin_jsonpath_make_entry_node(UInt32GetDatum(hash)); + nodes = lappend(nodes, node); + } + /* else: jsonb_path_ops doesn't support EXISTS queries */ + } + else + { + ExtractedPathEntry *pentry; + + /* append path entry nodes */ + for (pentry = path.entries; pentry; pentry = pentry->parent) + { + if (pentry->type == jpiKey) /* only keys are indexed */ + nodes = lappend(nodes, + gin_jsonpath_make_entry_node(pentry->entry)); + } + + if (scalar) + { + /* append scalar node for equality queries */ + JsonPathNode *node; + ExtractedPathEntry *last = path.entries; + GinTernaryValue lastIsArrayAccessor = !last ? GIN_FALSE : + last->type == jpiIndexArray || + last->type == jpiAnyArray ? GIN_TRUE : + last->type == jpiAny ? GIN_MAYBE : GIN_FALSE; + + /* + * Create OR-node when the string scalar can be matched as a key + * and a non-key. It is possible in lax mode where arrays are + * automatically unwrapped, or in strict mode for jpiAny items. + */ + if (scalar->type == jbvString && + (cxt->lax || lastIsArrayAccessor == GIN_MAYBE)) + node = gin_jsonpath_make_expr_node_binary(eOr, + gin_jsonpath_make_entry_node_scalar(scalar, true), + gin_jsonpath_make_entry_node_scalar(scalar, false)); + else + node = gin_jsonpath_make_entry_node_scalar(scalar, + scalar->type == jbvString && + lastIsArrayAccessor == GIN_TRUE); + + nodes = lappend(nodes, node); + } + } + + if (list_length(nodes) <= 0) + return NULL; /* need full scan for EXISTS($) queries without filters */ + + if (list_length(nodes) == 1) + return linitial(nodes); /* avoid extra AND-node */ + + /* construct AND-node for path with filters */ + return gin_jsonpath_make_expr_node_args(eAnd, nodes); +} + +/* Recursively extract nodes from the boolean jsonpath expression. */ +static JsonPathNode * +gin_extract_jsonpath_expr(JsonPathExtractionContext *cxt, JsonPathItem *jsp, + ExtractedJsonPath path, bool not) +{ + check_stack_depth(); + + switch (jsp->type) + { + case jpiAnd: + case jpiOr: + { + JsonPathItem arg; + JsonPathNode *larg; + JsonPathNode *rarg; + JsonPathNodeType type; + + jspGetLeftArg(jsp, &arg); + larg = gin_extract_jsonpath_expr(cxt, &arg, path, not); + + jspGetRightArg(jsp, &arg); + rarg = gin_extract_jsonpath_expr(cxt, &arg, path, not); + + if (!larg || !rarg) + { + if (jsp->type == jpiOr) + return NULL; + return larg ? larg : rarg; + } + + type = not ^ (jsp->type == jpiAnd) ? eAnd : eOr; + + return gin_jsonpath_make_expr_node_binary(type, larg, rarg); + } + + case jpiNot: + { + JsonPathItem arg; + + jspGetArg(jsp, &arg); + + return gin_extract_jsonpath_expr(cxt, &arg, path, !not); + } + + case jpiExists: + { + JsonPathItem arg; + + if (not) + return NULL; + + jspGetArg(jsp, &arg); + + return gin_extract_jsonpath_node(cxt, &arg, path, NULL); + } + + case jpiEqual: + { + JsonPathItem leftItem; + JsonPathItem rightItem; + JsonPathItem *pathItem; + JsonPathItem *scalarItem; + JsonbValue scalar; + + if (not) + return NULL; + + jspGetLeftArg(jsp, &leftItem); + jspGetRightArg(jsp, &rightItem); + + if (jspIsScalar(leftItem.type)) + { + scalarItem = &leftItem; + pathItem = &rightItem; + } + else if (jspIsScalar(rightItem.type)) + { + scalarItem = &rightItem; + pathItem = &leftItem; + } + else + return NULL; /* at least one operand should be a scalar */ + + switch (scalarItem->type) + { + case jpiNull: + scalar.type = jbvNull; + break; + case jpiBool: + scalar.type = jbvBool; + scalar.val.boolean = !!*scalarItem->content.value.data; + break; + case jpiNumeric: + scalar.type = jbvNumeric; + scalar.val.numeric = + (Numeric) scalarItem->content.value.data; + break; + case jpiString: + scalar.type = jbvString; + scalar.val.string.val = scalarItem->content.value.data; + scalar.val.string.len = scalarItem->content.value.datalen; + break; + default: + elog(ERROR, "invalid scalar jsonpath item type: %d", + scalarItem->type); + return NULL; + } + + return gin_extract_jsonpath_node(cxt, pathItem, path, &scalar); + } + + default: + return NULL; + } +} + +/* Recursively emit all GIN entries found in the node tree */ +static void +gin_jsonpath_emit_entries(JsonPathNode *node, GinEntries *entries) +{ + check_stack_depth(); + + switch (node->type) + { + case eEntry: + /* replace datum with its index in the array */ + node->val.entryIndex = + gin_entries_add(entries, node->val.entryDatum); + break; + + case eOr: + case eAnd: + { + int i; + + for (i = 0; i < node->val.nargs; i++) + gin_jsonpath_emit_entries(node->args[i], entries); + + break; + } + } +} + +static Datum * +gin_extract_jsonpath_query(JsonPath *jp, StrategyNumber strat, bool pathOps, + int32 *nentries, Pointer **extra_data) +{ + JsonPathExtractionContext cxt; + JsonPathItem root; + JsonPathNode *node; + ExtractedJsonPath path = { 0 }; + GinEntries entries = { 0 }; + + gin_jsonpath_init_context(&cxt, pathOps, (jp->header & JSONPATH_LAX) != 0); + + jspInit(&root, jp); + + node = strat == JsonbJsonpathExistsStrategyNumber + ? gin_extract_jsonpath_node(&cxt, &root, path, NULL) + : gin_extract_jsonpath_expr(&cxt, &root, path, false); + + if (!node) + { + *nentries = 0; + return NULL; + } + + gin_jsonpath_emit_entries(node, &entries); + + *nentries = entries.count; + if (!*nentries) + return NULL; + + *extra_data = palloc0(sizeof(**extra_data) * entries.count); + **extra_data = (Pointer) node; + + return entries.buf; +} + +static GinTernaryValue +gin_execute_jsonpath(JsonPathNode *node, void *check, bool ternary) +{ + GinTernaryValue res; + GinTernaryValue v; + int i; + + switch (node->type) + { + case eAnd: + res = GIN_TRUE; + for (i = 0; i < node->val.nargs; i++) + { + v = gin_execute_jsonpath(node->args[i], check, ternary); + if (v == GIN_FALSE) + return GIN_FALSE; + else if (v == GIN_MAYBE) + res = GIN_MAYBE; + } + return res; + + case eOr: + res = GIN_FALSE; + for (i = 0; i < node->val.nargs; i++) + { + v = gin_execute_jsonpath(node->args[i], check, ternary); + if (v == GIN_TRUE) + return GIN_TRUE; + else if (v == GIN_MAYBE) + res = GIN_MAYBE; + } + return res; + + case eEntry: + { + int index = node->val.entryIndex; + bool maybe = ternary + ? ((GinTernaryValue *) check)[index] != GIN_FALSE + : ((bool *) check)[index]; + + return maybe ? GIN_MAYBE : GIN_FALSE; + } + + default: + elog(ERROR, "invalid jsonpath gin node type: %d", node->type); + return GIN_FALSE; + } } Datum @@ -181,6 +744,18 @@ gin_extract_jsonb_query(PG_FUNCTION_ARGS) if (j == 0 && strategy == JsonbExistsAllStrategyNumber) *searchMode = GIN_SEARCH_MODE_ALL; } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPath *jp = PG_GETARG_JSONPATH_P(0); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + + entries = gin_extract_jsonpath_query(jp, strategy, false, nentries, + extra_data); + + if (!entries) + *searchMode = GIN_SEARCH_MODE_ALL; + } else { elog(ERROR, "unrecognized strategy number: %d", strategy); @@ -199,7 +774,7 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS) /* Jsonb *query = PG_GETARG_JSONB_P(2); */ int32 nkeys = PG_GETARG_INT32(3); - /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); bool *recheck = (bool *) PG_GETARG_POINTER(5); bool res = true; int32 i; @@ -256,6 +831,15 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS) } } } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPathNode *node = (JsonPathNode *) extra_data[0]; + + *recheck = true; + res = nkeys <= 0 || + gin_execute_jsonpath(node, check, false) != GIN_FALSE; + } else elog(ERROR, "unrecognized strategy number: %d", strategy); @@ -270,8 +854,7 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS) /* Jsonb *query = PG_GETARG_JSONB_P(2); */ int32 nkeys = PG_GETARG_INT32(3); - - /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); GinTernaryValue res = GIN_MAYBE; int32 i; @@ -308,6 +891,12 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS) } } } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + res = nkeys <= 0 ? GIN_MAYBE : + gin_execute_jsonpath((JsonPathNode *) extra_data[0], check, true); + } else elog(ERROR, "unrecognized strategy number: %d", strategy); @@ -331,14 +920,13 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) { Jsonb *jb = PG_GETARG_JSONB_P(0); int32 *nentries = (int32 *) PG_GETARG_POINTER(1); - int total = 2 * JB_ROOT_COUNT(jb); + int total = JB_ROOT_COUNT(jb); JsonbIterator *it; JsonbValue v; JsonbIteratorToken r; PathHashStack tail; PathHashStack *stack; - int i = 0; - Datum *entries; + GinEntries entries; /* If the root level is empty, we certainly have no keys */ if (total == 0) @@ -348,7 +936,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) } /* Otherwise, use 2 * root count as initial estimate of result size */ - entries = (Datum *) palloc(sizeof(Datum) * total); + gin_entries_init(&entries, 2 * total); /* We keep a stack of partial hashes corresponding to parent key levels */ tail.parent = NULL; @@ -361,13 +949,6 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) { PathHashStack *parent; - /* Since we recurse into the object, we might need more space */ - if (i >= total) - { - total *= 2; - entries = (Datum *) repalloc(entries, sizeof(Datum) * total); - } - switch (r) { case WJB_BEGIN_ARRAY: @@ -398,7 +979,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) /* mix the element or value's hash into the prepared hash */ JsonbHashScalarValue(&v, &stack->hash); /* and emit an index entry */ - entries[i++] = UInt32GetDatum(stack->hash); + gin_entries_add(&entries, UInt32GetDatum(stack->hash)); /* reset hash for next key, value, or sub-object */ stack->hash = stack->parent->hash; break; @@ -419,9 +1000,9 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) } } - *nentries = i; + *nentries = entries.count; - PG_RETURN_POINTER(entries); + PG_RETURN_POINTER(entries.buf); } Datum @@ -432,18 +1013,35 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS) int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); Datum *entries; - if (strategy != JsonbContainsStrategyNumber) - elog(ERROR, "unrecognized strategy number: %d", strategy); + if (strategy == JsonbContainsStrategyNumber) + { + /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */ + entries = (Datum *) + DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path, + PG_GETARG_DATUM(0), + PointerGetDatum(nentries))); - /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */ - entries = (Datum *) - DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path, - PG_GETARG_DATUM(0), - PointerGetDatum(nentries))); + /* ... although "contains {}" requires a full index scan */ + if (*nentries == 0) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPath *jp = PG_GETARG_JSONPATH_P(0); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); - /* ... although "contains {}" requires a full index scan */ - if (*nentries == 0) - *searchMode = GIN_SEARCH_MODE_ALL; + entries = gin_extract_jsonpath_query(jp, strategy, true, nentries, + extra_data); + + if (!entries) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else + { + elog(ERROR, "unrecognized strategy number: %d", strategy); + entries = NULL; + } PG_RETURN_POINTER(entries); } @@ -456,32 +1054,42 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS) /* Jsonb *query = PG_GETARG_JSONB_P(2); */ int32 nkeys = PG_GETARG_INT32(3); - - /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); bool *recheck = (bool *) PG_GETARG_POINTER(5); bool res = true; int32 i; - if (strategy != JsonbContainsStrategyNumber) - elog(ERROR, "unrecognized strategy number: %d", strategy); - - /* - * jsonb_path_ops is necessarily lossy, not only because of hash - * collisions but also because it doesn't preserve complete information - * about the structure of the JSON object. Besides, there are some - * special rules around the containment of raw scalars in arrays that are - * not handled here. So we must always recheck a match. However, if not - * all of the keys are present, the tuple certainly doesn't match. - */ - *recheck = true; - for (i = 0; i < nkeys; i++) + if (strategy == JsonbContainsStrategyNumber) { - if (!check[i]) + /* + * jsonb_path_ops is necessarily lossy, not only because of hash + * collisions but also because it doesn't preserve complete information + * about the structure of the JSON object. Besides, there are some + * special rules around the containment of raw scalars in arrays that are + * not handled here. So we must always recheck a match. However, if not + * all of the keys are present, the tuple certainly doesn't match. + */ + *recheck = true; + for (i = 0; i < nkeys; i++) { - res = false; - break; + if (!check[i]) + { + res = false; + break; + } } } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + JsonPathNode *node = (JsonPathNode *) extra_data[0]; + + *recheck = true; + res = nkeys <= 0 || + gin_execute_jsonpath(node, check, false) != GIN_FALSE; + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); PG_RETURN_BOOL(res); } @@ -494,27 +1102,34 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS) /* Jsonb *query = PG_GETARG_JSONB_P(2); */ int32 nkeys = PG_GETARG_INT32(3); - - /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); GinTernaryValue res = GIN_MAYBE; int32 i; - if (strategy != JsonbContainsStrategyNumber) - elog(ERROR, "unrecognized strategy number: %d", strategy); - - /* - * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this - * corresponds to always forcing recheck in the regular consistent - * function, for the reasons listed there. - */ - for (i = 0; i < nkeys; i++) + if (strategy == JsonbContainsStrategyNumber) { - if (check[i] == GIN_FALSE) + /* + * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this + * corresponds to always forcing recheck in the regular consistent + * function, for the reasons listed there. + */ + for (i = 0; i < nkeys; i++) { - res = GIN_FALSE; - break; + if (check[i] == GIN_FALSE) + { + res = GIN_FALSE; + break; + } } } + else if (strategy == JsonbJsonpathPredicateStrategyNumber || + strategy == JsonbJsonpathExistsStrategyNumber) + { + res = nkeys <= 0 ? GIN_MAYBE : + gin_execute_jsonpath((JsonPathNode *) extra_data[0], check, true); + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); PG_RETURN_GIN_TERNARY_VALUE(res); } diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index fb58f774b9..c5771e26dc 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1489,11 +1489,23 @@ { amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb', amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)', amopmethod => 'gin' }, +{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb', + amoprighttype => 'jsonpath', amopstrategy => '15', + amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' }, +{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb', + amoprighttype => 'jsonpath', amopstrategy => '16', + amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' }, # GIN jsonb_path_ops { amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb', amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)', amopmethod => 'gin' }, +{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb', + amoprighttype => 'jsonpath', amopstrategy => '15', + amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' }, +{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb', + amoprighttype => 'jsonpath', amopstrategy => '16', + amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' }, # SP-GiST range_ops { amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange', diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index b306b2613c..144b8b0956 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -34,6 +34,9 @@ typedef enum #define JsonbExistsStrategyNumber 9 #define JsonbExistsAnyStrategyNumber 10 #define JsonbExistsAllStrategyNumber 11 +#define JsonbJsonpathExistsStrategyNumber 15 +#define JsonbJsonpathPredicateStrategyNumber 16 + /* * In the standard jsonb_ops GIN opclass for jsonb, we choose to index both diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index 73e3f317f0..c808fb1881 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -35,6 +35,8 @@ typedef struct #define PG_GETARG_JSONPATH_P_COPY(x) DatumGetJsonPathPCopy(PG_GETARG_DATUM(x)) #define PG_RETURN_JSONPATH_P(p) PG_RETURN_POINTER(p) +#define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool) + /* * All node's type of jsonpath expression */ diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index e5c2577dc2..6ffe059742 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2708,6 +2708,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; 42 (1 row) +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)'; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)'; + count +------- + 42 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + count +------- + 0 +(1 row) + CREATE INDEX jidx ON testjsonb USING gin (j); SET enable_seqscan = off; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; @@ -2783,6 +2891,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; 42 (1 row) +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; + QUERY PLAN +----------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on testjsonb + Recheck Cond: (j @~ '($."wait" == null)'::jsonpath) + -> Bitmap Index Scan on jidx + Index Cond: (j @~ '($."wait" == null)'::jsonpath) +(5 rows) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)'; + count +------- + 337 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)'; + count +------- + 42 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on testjsonb + Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath) + -> Bitmap Index Scan on jidx + Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath) +(5 rows) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + count +------- + 0 +(1 row) + -- array exists - array elements should behave as keys (for GIN index scans too) CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; @@ -2933,6 +3231,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}'; 1012 (1 row) +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; + count +------- + 1012 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on testjsonb + Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath) + -> Bitmap Index Scan on jidx + Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath) +(5 rows) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; + count +------- + 15 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; + count +------- + 2 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$'; + count +------- + 1012 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; + count +------- + 194 +(1 row) + +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + count +------- + 0 +(1 row) + RESET enable_seqscan; DROP INDEX jidx; -- nested tests diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index a1e18a6ceb..dae99a0479 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1825,6 +1825,8 @@ ORDER BY 1, 2, 3; 2742 | 9 | ? 2742 | 10 | ?| 2742 | 11 | ?& + 2742 | 15 | @? + 2742 | 16 | @~ 3580 | 1 | < 3580 | 1 | << 3580 | 2 | &< @@ -1889,7 +1891,7 @@ ORDER BY 1, 2, 3; 4000 | 26 | >> 4000 | 27 | >>= 4000 | 28 | ^@ -(122 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index d0ab6026ec..aa2adec901 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -733,6 +733,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public'; SELECT count(*) FROM testjsonb WHERE j ? 'bar'; SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; CREATE INDEX jidx ON testjsonb USING gin (j); SET enable_seqscan = off; @@ -751,6 +769,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar'; SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled']; SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))'; +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)'; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + -- array exists - array elements should behave as keys (for GIN index scans too) CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; @@ -800,6 +851,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; -- exercise GIN_SEARCH_MODE_ALL SELECT count(*) FROM testjsonb WHERE j @> '{}'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))'; +SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"'; +SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))'; +SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)'; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)'; +SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)'; +SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")'; +SELECT count(*) FROM testjsonb WHERE j @? '$'; +SELECT count(*) FROM testjsonb WHERE j @? '$.public'; +SELECT count(*) FROM testjsonb WHERE j @? '$.bar'; + RESET enable_seqscan; DROP INDEX jidx; From b505bca935396b7910e180f166b35b6566efd904 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 15:54:51 +0300 Subject: [PATCH 65/97] Add jsonpath support for json type --- src/backend/utils/adt/Makefile | 4 +- src/backend/utils/adt/json.c | 835 ++++++++- src/backend/utils/adt/jsonb_util.c | 16 +- src/backend/utils/adt/jsonpath_exec.c | 18 +- src/backend/utils/adt/jsonpath_json.c | 22 + src/include/catalog/pg_operator.dat | 14 + src/include/catalog/pg_proc.dat | 29 + src/include/utils/jsonapi.h | 61 + src/include/utils/jsonb.h | 14 +- src/include/utils/jsonpath_json.h | 118 ++ src/test/regress/expected/json_jsonpath.out | 1696 +++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/json_jsonpath.sql | 370 ++++ 14 files changed, 3166 insertions(+), 34 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/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 41b00fd4cd..e2ad685b3d 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_scan.o jsonpath.o jsonpath_exec.o \ + jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ @@ -47,6 +47,8 @@ jsonpath_gram.h: jsonpath_gram.c ; # Force these dependencies to be known even without dependency info built: jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h +jsonpath_json.o: jsonpath_exec.c + # jsonpath_gram.c, jsonpath_gram.h, 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 79eeac76ba..034c300826 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,22 @@ lex_peek(JsonLexContext *lex) return lex->token_type; } +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 +160,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; } @@ -2572,3 +2577,803 @@ json_typeof(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(type)); } + +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; +} + +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; +} + +static inline JsonIterator * +JsonIteratorFreeAndGetParent(JsonIterator *it) +{ + JsonIterator *parent = it->parent; + + pfree(it); + + return parent; +} + +/* + * Free a whole stack of JsonIterator iterators. + */ +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.uniquified = 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; +} + +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: + appendStringInfoString(buf, DatumGetCString(DirectFunctionCall1( + numeric_out, NumericGetDatum(jbv->val.numeric)))); + 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 jbvDatetime: + { + char dtbuf[MAXDATELEN + 1]; + + JsonEncodeDateTime(dtbuf, + jbv->val.datetime.value, + jbv->val.datetime.typid); + + escape_json(buf, dtbuf); + + 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 "uniquified" 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 "uniquified" flag of objects */ + if (seq == WJB_BEGIN_OBJECT) + (*pstate)->contVal.val.object.uniquified = 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 "uniquified" flag of objects */ + if (tok == WJB_BEGIN_OBJECT) + (*pstate)->contVal.val.object.uniquified = false; + } + + return res; +} + +JsonbValue * +JsonExtractScalar(JsonContainer *jbc, JsonbValue *res) +{ + JsonIterator *it = JsonIteratorInit(jbc); + JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY; + JsonbValue tmp; + + 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 res; +} + +/* + * 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 89168fb4ca..fd2da1f68f 100644 --- a/src/backend/utils/adt/jsonb_util.c +++ b/src/backend/utils/adt/jsonb_util.c @@ -39,7 +39,6 @@ static void fillJsonbValue(JsonbContainer *container, int index, char *base_addr, uint32 offset, JsonbValue *result); -static bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b); 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 +57,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. @@ -546,7 +541,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) { @@ -584,6 +579,7 @@ pushJsonbValueScalar(JsonbParseState **pstate, JsonbIteratorToken seq, (*pstate)->size = 4; (*pstate)->contVal.val.object.pairs = palloc(sizeof(JsonbPair) * (*pstate)->size); + (*pstate)->contVal.val.object.uniquified = true; break; case WJB_KEY: Assert(scalarVal->type == jbvString); @@ -826,6 +822,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.uniquified = true; /* * v->val.object.pairs is not actually set, because we aren't @@ -1299,7 +1296,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) @@ -1779,7 +1776,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; @@ -1843,6 +1840,9 @@ uniqueifyJsonbObject(JsonbValue *object) Assert(object->type == jbvObject); + if (!object->val.object.uniquified) + 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 0de0f12ab1..7433ae39e8 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -23,8 +23,14 @@ #include "utils/jsonpath.h" #include "utils/varlena.h" +#ifdef JSONPATH_JSON_C +#define JSONXOID JSONOID +#else +#define JSONXOID JSONBOID + /* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */ ErrorData jperNotFound[1]; +#endif typedef struct JsonPathExecContext @@ -163,6 +169,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) return lfirst(it->lcell); } +#ifndef JSONPATH_JSON_C /* * Initialize a binary JsonbValue with the given jsonb container. */ @@ -175,6 +182,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } +#endif /* * Transform a JsonbValue into a binary JsonbValue by encoding it to a @@ -287,7 +295,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) value->val.datetime.tz = 0; value->val.datetime.value = computedValue; break; - case JSONBOID: + case JSONXOID: { Jsonb *jb = DatumGetJsonbP(computedValue); @@ -353,7 +361,7 @@ JsonbType(JsonbValue *jb) if (jb->type == jbvBinary) { - JsonbContainer *jbc = jb->val.binary.data; + JsonbContainer *jbc = (void *) jb->val.binary.data; if (JsonContainerIsScalar(jbc)) type = jbvScalar; @@ -378,7 +386,7 @@ JsonbTypeName(JsonbValue *jb) if (jb->type == jbvBinary) { - JsonbContainer *jbc = jb->val.binary.data; + JsonbContainer *jbc = (void *) jb->val.binary.data; if (JsonContainerIsScalar(jbc)) jb = JsonbExtractScalar(jbc, &jbvbuf); @@ -439,7 +447,7 @@ JsonbArraySize(JsonbValue *jb) if (jb->type == jbvBinary) { - JsonbContainer *jbc = jb->val.binary.data; + JsonbContainer *jbc = (void *) jb->val.binary.data; if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc)) return JsonContainerSize(jbc); @@ -2433,7 +2441,7 @@ makePassingVars(Jsonb *jb) jpv->cb_arg = v.val.numeric; break; case jbvBinary: - jpv->typid = JSONBOID; + jpv->typid = JSONXOID; jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v))); break; default: diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c new file mode 100644 index 0000000000..91b3e7b8b2 --- /dev/null +++ b/src/backend/utils/adt/jsonpath_json.c @@ -0,0 +1,22 @@ +#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_jsonpath_exists2 json_jsonpath_exists2 +#define jsonb_jsonpath_exists3 json_jsonpath_exists3 +#define jsonb_jsonpath_predicate2 json_jsonpath_predicate2 +#define jsonb_jsonpath_predicate3 json_jsonpath_predicate3 +#define jsonb_jsonpath_query2 json_jsonpath_query2 +#define jsonb_jsonpath_query3 json_jsonpath_query3 +#define jsonb_jsonpath_query_wrapped2 json_jsonpath_query_wrapped2 +#define jsonb_jsonpath_query_wrapped3 json_jsonpath_query_wrapped3 + +#include "jsonpath_exec.c" diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 3296233503..91b03890cf 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3350,5 +3350,19 @@ { oid => '6122', descr => 'jsonpath items wrapped', oprname => '@#', oprleft => 'jsonb', oprright => 'jsonpath', oprresult => 'jsonb', oprcode => 'jsonpath_query_wrapped(jsonb,jsonpath)' }, +{ oid => '6070', descr => 'jsonpath items', + oprname => '@*', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'json', oprcode => 'jsonpath_query(json,jsonpath)' }, +{ oid => '6071', descr => 'jsonpath exists', + oprname => '@?', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'bool', oprcode => 'jsonpath_exists(json,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6108', descr => 'jsonpath predicate', + oprname => '@~', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'bool', oprcode => 'jsonpath_predicate(json,jsonpath)', + oprrest => 'contsel', oprjoin => 'contjoinsel' }, +{ oid => '6123', descr => 'jsonpath items wrapped', + oprname => '@#', oprleft => 'json', oprright => 'jsonpath', + oprresult => 'json', oprcode => 'jsonpath_query_wrapped(json,jsonpath)' }, ] diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index ec7e0e538c..a6e4436327 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9332,6 +9332,35 @@ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_predicate3' }, +{ oid => '6043', descr => 'implementation of @? operator', + proname => 'jsonpath_exists', prorettype => 'bool', + proargtypes => 'json jsonpath', prosrc => 'json_jsonpath_exists2' }, +{ oid => '6044', descr => 'implementation of @* operator', + proname => 'jsonpath_query', prorows => '1000', proretset => 't', + prorettype => 'json', proargtypes => 'json jsonpath', + prosrc => 'json_jsonpath_query2' }, +{ oid => '6126', descr => 'implementation of @# operator', + proname => 'jsonpath_query_wrapped', prorettype => 'json', + proargtypes => 'json jsonpath', prosrc => 'json_jsonpath_query_wrapped2' }, +{ oid => '6045', descr => 'jsonpath exists test', + proname => 'jsonpath_exists', prorettype => 'bool', + proargtypes => 'json jsonpath json', prosrc => 'json_jsonpath_exists3' }, +{ oid => '6046', descr => 'jsonpath query', + proname => 'jsonpath_query', prorows => '1000', proretset => 't', + prorettype => 'json', proargtypes => 'json jsonpath json', + prosrc => 'json_jsonpath_query3' }, +{ oid => '6127', descr => 'jsonpath query with conditional wrapper', + proname => 'jsonpath_query_wrapped', prorettype => 'json', + proargtypes => 'json jsonpath json', + prosrc => 'json_jsonpath_query_wrapped3' }, +{ oid => '6049', descr => 'implementation of @~ operator', + proname => 'jsonpath_predicate', prorettype => 'bool', + proargtypes => 'json jsonpath', prosrc => 'json_jsonpath_predicate2' }, +{ oid => '6069', descr => 'jsonpath predicate test', + proname => 'jsonpath_predicate', prorettype => 'bool', + proargtypes => 'json jsonpath json', + prosrc => 'json_jsonpath_predicate3' }, + # 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 803ff667cc..6ef601f061 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 @@ -163,4 +206,22 @@ extern text *transform_json_string_values(text *json, void *action_state, extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, int *tz); +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 JsonbValue *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 144b8b0956..2ea1ec1ac8 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 @@ -276,6 +276,8 @@ struct JsonbValue struct { int nPairs; /* 1 pair, 2 elements */ + bool uniquified; /* Should we sort pairs by key name and + * remove duplicate keys? */ JsonbPair *pairs; } object; /* Associative container type */ @@ -371,6 +373,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, @@ -379,6 +383,8 @@ 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..a751540116 --- /dev/null +++ b/src/include/utils/jsonpath_json.h @@ -0,0 +1,118 @@ +/*------------------------------------------------------------------------- + * + * 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 +#define JsonbPathExists JsonPathExists +#define JsonbPathQuery JsonPathQuery +#define JsonbPathValue JsonPathValue +#define JsonbTableRoutine JsonTableRoutine + +#define JsonbWrapItemInArray JsonWrapItemInArray +#define JsonbWrapItemsInArray JsonWrapItemsInArray +#define JsonbArraySize JsonArraySize +#define JsonValueListConcat JsonValueListConcatJson +#define jspRecursiveExecute jspRecursiveExecuteJson +#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson +#define jspCompareItems jspCompareItemsJson + +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..d7c63dcbde --- /dev/null +++ b/src/test/regress/expected/json_jsonpath.out @@ -0,0 +1,1696 @@ +select json '{"a": 12}' @? '$.a.b'; + ?column? +---------- + f +(1 row) + +select json '{"a": 12}' @? '$.b'; + ?column? +---------- + f +(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 '{}' @? '$.*'; + ?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 '[1]' @* 'strict $[1]'; +ERROR: Invalid SQL/JSON subscript +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 '[1]' @* 'strict $[1.2]'; +ERROR: Invalid SQL/JSON subscript +select json '{}' @* 'strict $[0.3]'; +ERROR: SQL/JSON array not found +select json '{}' @? 'lax $[0.3]'; + ?column? +---------- + t +(1 row) + +select json '{}' @* 'strict $[1.2]'; +ERROR: SQL/JSON array not found +select json '{}' @? 'lax $[1.2]'; + ?column? +---------- + f +(1 row) + +select json '{}' @* 'strict $[-2 to 3]'; +ERROR: SQL/JSON array not found +select json '{}' @? 'lax $[-2 to 3]'; + ?column? +---------- + t +(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 '{"a": 12, "b": {"a": 13}}' @* '$.a'; + ?column? +---------- + 12 +(1 row) + +select json '{"a": 12, "b": {"a": 13}}' @* '$.b'; + ?column? +----------- + {"a": 13} +(1 row) + +select json '{"a": 12, "b": {"a": 13}}' @* '$.*'; + ?column? +----------- + 12 + {"a": 13} +(2 rows) + +select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; + ?column? +---------- + 13 +(1 row) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a'; + ?column? +---------- + 13 +(1 row) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*'; + ?column? +---------- + 13 + 14 +(2 rows) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a'; + ?column? +---------- +(0 rows) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a'; + ?column? +---------- + 13 +(1 row) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a'; + ?column? +---------- +(0 rows) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a'; + ?column? +---------- + 13 +(1 row) + +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; + ?column? +---------- + 13 +(1 row) + +select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]'; + ?column? +----------- + {"a": 13} + {"b": 14} + "ccc" +(3 rows) + +select json '1' @* 'lax $[0]'; + ?column? +---------- + 1 +(1 row) + +select json '1' @* 'lax $[*]'; + ?column? +---------- + 1 +(1 row) + +select json '{}' @* 'lax $[0]'; + ?column? +---------- + {} +(1 row) + +select json '[1]' @* 'lax $[0]'; + ?column? +---------- + 1 +(1 row) + +select json '[1]' @* 'lax $[*]'; + ?column? +---------- + 1 +(1 row) + +select json '[1,2,3]' @* 'lax $[*]'; + ?column? +---------- + 1 + 2 + 3 +(3 rows) + +select json '[]' @* '$[last]'; + ?column? +---------- +(0 rows) + +select json '[]' @* 'strict $[last]'; +ERROR: Invalid SQL/JSON subscript +select json '[1]' @* '$[last]'; + ?column? +---------- + 1 +(1 row) + +select json '{}' @* 'lax $[last]'; + ?column? +---------- + {} +(1 row) + +select json '[1,2,3]' @* '$[last]'; + ?column? +---------- + 3 +(1 row) + +select json '[1,2,3]' @* '$[last - 1]'; + ?column? +---------- + 2 +(1 row) + +select json '[1,2,3]' @* '$[last ? (@.type() == "number")]'; + ?column? +---------- + 3 +(1 row) + +select json '[1,2,3]' @* '$[last ? (@.type() == "string")]'; +ERROR: Invalid SQL/JSON subscript +select * from jsonpath_query(json '{"a": 10}', '$'); + jsonpath_query +---------------- + {"a": 10} +(1 row) + +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)'); +ERROR: could not find 'value' passed variable +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); + jsonpath_query +---------------- + {"a": 10} +(1 row) + +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); + jsonpath_query +---------------- +(0 rows) + +select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- + 10 +(1 row) + +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- + 10 + 11 + 12 +(3 rows) + +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}'); + jsonpath_query +---------------- + 10 + 11 +(2 rows) + +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); + jsonpath_query +---------------- + 10 + 11 + 12 +(3 rows) + +select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); + jsonpath_query +---------------- + "1" +(1 row) + +select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); + jsonpath_query +---------------- + "1" +(1 row) + +select json '[1, "2", null]' @* '$[*] ? (@ != null)'; + ?column? +---------- + 1 + "2" +(2 rows) + +select json '[1, "2", null]' @* '$[*] ? (@ == null)'; + ?column? +---------- + null +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**'; + ?column? +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + +select json '{"a": {"b": 1}}' @* 'lax $.**{1}'; + ?column? +---------- + {"b": 1} +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{1,}'; + ?column? +---------- + {"b": 1} + 1 +(2 rows) + +select json '{"a": {"b": 1}}' @* 'lax $.**{2}'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{2,}'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{3,}'; + ?column? +---------- +(0 rows) + +select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; + ?column? +---------- +(0 rows) + +select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; + ?column? +---------- +(0 rows) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; + ?column? +---------- +(0 rows) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; + ?column? +---------- + 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,}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"b": 1}}' @? '$.**{1,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,}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; + ?column? +---------- + t +(1 row) + +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; + ?column? +---------- + {"x": 2} +(1 row) + +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; + ?column? +---------- +(0 rows) + +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; + ?column? +---------- + {"x": 2} +(1 row) + +--test ternary logic +select + x, y, + jsonpath_query( + json '[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, + jsonpath_query( + json '[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 '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)'; + ?column? +------------------ + {"a": 2, "b": 1} +(1 row) + +select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))'; + ?column? +------------------ + {"a": 2, "b": 1} +(1 row) + +select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)'; + ?column? +------------------ + {"a": 2, "b": 1} +(1 row) + +select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))'; + ?column? +------------------ + {"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 '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)'; + ?column? +---------- + 1 + 2 + 3 +(3 rows) + +select json '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)'; + ?column? +---------- + 0 +(1 row) + +select json '0' @* '1 / $'; +ERROR: division by zero +-- unwrapping of operator arguments in lax mode +select json '{"a": [2]}' @* 'lax $.a * 3'; + ?column? +---------- + 6 +(1 row) + +select json '{"a": [2]}' @* 'lax $.a + 3'; + ?column? +---------- + 5 +(1 row) + +select json '{"a": [2, 3, 4]}' @* 'lax -$.a'; + ?column? +---------- + -2 + -3 + -4 +(3 rows) + +-- should fail +select json '{"a": [1, 2]}' @* 'lax $.a * 3'; +ERROR: Singleton SQL/JSON item required +-- extension: boolean expressions +select json '2' @* '$ > 1'; + ?column? +---------- + true +(1 row) + +select json '2' @* '$ <= 1'; + ?column? +---------- + false +(1 row) + +select json '2' @* '$ == "2"'; + ?column? +---------- + null +(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]' @~ '$[*]'; +ERROR: Singleton SQL/JSON item required +select json '[]' @~ '$[*]'; +ERROR: Singleton SQL/JSON item required +select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); + jsonpath_predicate +-------------------- + f +(1 row) + +select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + jsonpath_predicate +-------------------- + t +(1 row) + +select json '[null,1,true,"a",[],{}]' @* '$.type()'; + ?column? +---------- + "array" +(1 row) + +select json '[null,1,true,"a",[],{}]' @* 'lax $.type()'; + ?column? +---------- + "array" +(1 row) + +select json '[null,1,true,"a",[],{}]' @* '$[*].type()'; + ?column? +----------- + "null" + "number" + "boolean" + "string" + "array" + "object" +(6 rows) + +select json 'null' @* 'null.type()'; + ?column? +---------- + "null" +(1 row) + +select json 'null' @* 'true.type()'; + ?column? +----------- + "boolean" +(1 row) + +select json 'null' @* '123.type()'; + ?column? +---------- + "number" +(1 row) + +select json 'null' @* '"123".type()'; + ?column? +---------- + "string" +(1 row) + +select json '{"a": 2}' @* '($.a - 5).abs() + 10'; + ?column? +---------- + 13 +(1 row) + +select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10'; + ?column? +---------- + 4 +(1 row) + +select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)'; + ?column? +---------- + true +(1 row) + +select json '[1, 2, 3]' @* '($[*] > 3).type()'; + ?column? +----------- + "boolean" +(1 row) + +select json '[1, 2, 3]' @* '($[*].a > 3).type()'; + ?column? +----------- + "boolean" +(1 row) + +select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()'; + ?column? +---------- + "null" +(1 row) + +select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()'; +ERROR: SQL/JSON array not found +select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()'; + ?column? +---------- + 1 + 1 + 1 + 1 + 0 + 1 + 3 + 1 + 1 +(9 rows) + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()'; + ?column? +---------- + 0 + 1 + 2 + 3.4 + 5.6 +(5 rows) + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()'; + ?column? +---------- + 0 + 1 + -2 + -4 + 5 +(5 rows) + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()'; + ?column? +---------- + 0 + 1 + -2 + -3 + 6 +(5 rows) + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()'; + ?column? +---------- + 0 + 1 + 2 + 3 + 6 +(5 rows) + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()'; + ?column? +---------- + "number" + "number" + "number" + "number" + "number" +(5 rows) + +select json '[{},1]' @* '$[*].keyvalue()'; +ERROR: SQL/JSON object not found +select json '{}' @* '$.keyvalue()'; + ?column? +---------- +(0 rows) + +select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()'; + ?column? +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()'; + ?column? +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()'; +ERROR: SQL/JSON object not found +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()'; + ?column? +------------------------------------- + {"key": "a", "value": 1} + {"key": "b", "value": [1, 2]} + {"key": "c", "value": {"a": "bbb"}} +(3 rows) + +select json 'null' @* '$.double()'; +ERROR: Non-numeric SQL/JSON item +select json 'true' @* '$.double()'; +ERROR: Non-numeric SQL/JSON item +select json '[]' @* '$.double()'; + ?column? +---------- +(0 rows) + +select json '[]' @* 'strict $.double()'; +ERROR: Non-numeric SQL/JSON item +select json '{}' @* '$.double()'; +ERROR: Non-numeric SQL/JSON item +select json '1.23' @* '$.double()'; + ?column? +---------- + 1.23 +(1 row) + +select json '"1.23"' @* '$.double()'; + ?column? +---------- + 1.23 +(1 row) + +select json '"1.23aaa"' @* '$.double()'; +ERROR: Non-numeric SQL/JSON item +select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")'; + ?column? +---------- + "abc" + "abcabc" +(2 rows) + +select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? +---------------------------- + ["", "a", "abc", "abcabc"] +(1 row) + +select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? +---------- +(0 rows) + +select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")'; + ?column? +---------- +(0 rows) + +select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)'; + ?column? +---------------------------- + ["abc", "abcabc", null, 1] +(1 row) + +select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")'; + ?column? +---------------------------- + [null, 1, "abc", "abcabc"] +(1 row) + +select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)'; + ?column? +---------------------------- + [null, 1, "abd", "abdabc"] +(1 row) + +select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)'; + ?column? +---------- + null + 1 +(2 rows) + +select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")'; + ?column? +---------- + "abc" + "abdacb" +(2 rows) + +select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'; + ?column? +---------- + "abc" + "aBdC" + "abdacb" +(3 rows) + +select json 'null' @* '$.datetime()'; +ERROR: Invalid argument for SQL/JSON datetime function +select json 'true' @* '$.datetime()'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '[]' @* '$.datetime()'; + ?column? +---------- +(0 rows) + +select json '[]' @* 'strict $.datetime()'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '{}' @* '$.datetime()'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '""' @* '$.datetime()'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; + ?column? +-------------- + "2017-03-10" +(1 row) + +select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; + ?column? +---------- + "date" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; + ?column? +-------------- + "2017-03-10" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()'; + ?column? +---------- + "date" +(1 row) + +select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()'; + ?column? +------------------------------- + "timestamp without time zone" +(1 row) + +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'; + ?column? +---------------------------- + "timestamp with time zone" +(1 row) + +select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()'; + ?column? +-------------------------- + "time without time zone" +(1 row) + +select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()'; + ?column? +----------------------- + "time with time zone" +(1 row) + +set time zone '+00'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; + ?column? +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-10T12:34:00+00:00" +(1 row) + +select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-10T07:34:00+00:00" +(1 row) + +select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-10T17:34:00+00:00" +(1 row) + +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? +----------------------------- + "2017-03-10T07:14:00+00:00" +(1 row) + +select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? +----------------------------- + "2017-03-10T17:54:00+00:00" +(1 row) + +select json '"12:34"' @* '$.datetime("HH24:MI")'; + ?column? +------------ + "12:34:00" +(1 row) + +select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00+00:00" +(1 row) + +select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00+05:00" +(1 row) + +select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00-05:00" +(1 row) + +select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? +------------------ + "12:34:00+05:20" +(1 row) + +select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? +------------------ + "12:34:00-05:20" +(1 row) + +set time zone '+10'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; + ?column? +----------------------- + "2017-03-10T12:34:00" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-10T12:34:00+10:00" +(1 row) + +select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-10T17:34:00+10:00" +(1 row) + +select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; + ?column? +----------------------------- + "2017-03-11T03:34:00+10:00" +(1 row) + +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? +----------------------------- + "2017-03-10T17:14:00+10:00" +(1 row) + +select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; + ?column? +----------------------------- + "2017-03-11T03:54:00+10:00" +(1 row) + +select json '"12:34"' @* '$.datetime("HH24:MI")'; + ?column? +------------ + "12:34:00" +(1 row) + +select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00+10:00" +(1 row) + +select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00+05:00" +(1 row) + +select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; + ?column? +------------------ + "12:34:00-05:00" +(1 row) + +select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? +------------------ + "12:34:00+05:20" +(1 row) + +select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + ?column? +------------------ + "12:34:00-05:20" +(1 row) + +set time zone default; +select json '"2017-03-10"' @* '$.datetime().type()'; + ?column? +---------- + "date" +(1 row) + +select json '"2017-03-10"' @* '$.datetime()'; + ?column? +-------------- + "2017-03-10" +(1 row) + +select json '"2017-03-10 12:34:56"' @* '$.datetime().type()'; + ?column? +------------------------------- + "timestamp without time zone" +(1 row) + +select json '"2017-03-10 12:34:56"' @* '$.datetime()'; + ?column? +----------------------- + "2017-03-10T12:34:56" +(1 row) + +select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; + ?column? +---------------------------- + "timestamp with time zone" +(1 row) + +select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; + ?column? +----------------------------- + "2017-03-10T01:34:56-08:00" +(1 row) + +select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; + ?column? +---------------------------- + "timestamp with time zone" +(1 row) + +select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; + ?column? +----------------------------- + "2017-03-10T01:24:56-08:00" +(1 row) + +select json '"12:34:56"' @* '$.datetime().type()'; + ?column? +-------------------------- + "time without time zone" +(1 row) + +select json '"12:34:56"' @* '$.datetime()'; + ?column? +------------ + "12:34:56" +(1 row) + +select json '"12:34:56 +3"' @* '$.datetime().type()'; + ?column? +----------------------- + "time with time zone" +(1 row) + +select json '"12:34:56 +3"' @* '$.datetime()'; + ?column? +------------------ + "12:34:56+03:00" +(1 row) + +select json '"12:34:56 +3:10"' @* '$.datetime().type()'; + ?column? +----------------------- + "time with time zone" +(1 row) + +select json '"12:34:56 +3:10"' @* '$.datetime()'; + ?column? +------------------ + "12:34:56+03:10" +(1 row) + +set time zone '+00'; +-- date comparison +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10" + "2017-03-10T00:00:00" + "2017-03-10T00:00:00+00:00" +(3 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10" + "2017-03-11" + "2017-03-10T00:00:00" + "2017-03-10T12:34:56" + "2017-03-10T00:00:00+00:00" +(5 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-09" + "2017-03-09T21:02:03+00:00" +(2 rows) + +-- time comparison +select json '["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"))'; + ?column? +------------------ + "12:35:00" + "12:35:00+00:00" +(2 rows) + +select json '["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"))'; + ?column? +------------------ + "12:35:00" + "12:36:00" + "12:35:00+00:00" +(3 rows) + +select json '["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"))'; + ?column? +------------------ + "12:34:00" + "12:35:00+01:00" + "13:35:00+01:00" +(3 rows) + +-- timetz comparison +select json '["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"))'; + ?column? +------------------ + "12:35:00+01:00" +(1 row) + +select json '["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"))'; + ?column? +------------------ + "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 '["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"))'; + ?column? +------------------ + "12:34:00+01:00" + "12:35:00+02:00" + "10:35:00" +(3 rows) + +-- timestamp comparison +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:35:00+00:00" +(2 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T12:35:00" + "2017-03-10T12:36:00" + "2017-03-10T12:35:00+00:00" + "2017-03-10T13:35:00+00:00" + "2017-03-11" +(5 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T12:34:00" + "2017-03-10T11:35:00+00:00" + "2017-03-10" +(3 rows) + +-- timestamptz comparison +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T11:35:00+00:00" + "2017-03-10T11:35:00" +(2 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T11:35:00+00:00" + "2017-03-10T11:36:00+00:00" + "2017-03-10T14:35:00+00:00" + "2017-03-10T11:35:00" + "2017-03-10T12:35:00" + "2017-03-11" +(6 rows) + +select json '["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"))'; + ?column? +----------------------------- + "2017-03-10T11:34:00+00:00" + "2017-03-10T10:35:00+00:00" + "2017-03-10T10:35:00" + "2017-03-10" +(4 rows) + +set time zone default; +-- jsonpath operators +SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]'; + ?column? +---------- + {"a": 1} + {"a": 2} +(2 rows) + +SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; + ?column? +---------- +(0 rows) + +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a'; + ?column? +---------- + [1, 2] +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; + ?column? +---------- + 1 +(1 row) + +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; + ?column? +---------- + +(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 '[{"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 afa8c35e4f..2e0cd2df8f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,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 jsonb_jsonpath +test: json jsonb json_encoding jsonpath json_jsonpath jsonb_jsonpath # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index bcb46a6126..494ccebe73 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -161,6 +161,7 @@ test: json test: jsonb test: json_encoding test: jsonpath +test: json_jsonpath test: jsonb_jsonpath test: indirect_toast test: equivclass diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql new file mode 100644 index 0000000000..1072165f48 --- /dev/null +++ b/src/test/regress/sql/json_jsonpath.sql @@ -0,0 +1,370 @@ +select json '{"a": 12}' @? '$.a.b'; +select json '{"a": 12}' @? '$.b'; +select json '{"a": {"a": 12}}' @? '$.a.a'; +select json '{"a": {"a": 12}}' @? '$.*.a'; +select json '{"b": {"a": 12}}' @? '$.*.a'; +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 '[1]' @* 'strict $[1]'; +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 '[1]' @* 'strict $[1.2]'; +select json '{}' @* 'strict $[0.3]'; +select json '{}' @? 'lax $[0.3]'; +select json '{}' @* 'strict $[1.2]'; +select json '{}' @? 'lax $[1.2]'; +select json '{}' @* '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[*])'; +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 '{"a": 12, "b": {"a": 13}}' @* '$.a'; +select json '{"a": 12, "b": {"a": 13}}' @* '$.b'; +select json '{"a": 12, "b": {"a": 13}}' @* '$.*'; +select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a'; +select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; +select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]'; +select json '1' @* 'lax $[0]'; +select json '1' @* 'lax $[*]'; +select json '{}' @* 'lax $[0]'; +select json '[1]' @* 'lax $[0]'; +select json '[1]' @* 'lax $[*]'; +select json '[1,2,3]' @* 'lax $[*]'; +select json '[]' @* '$[last]'; +select json '[]' @* 'strict $[last]'; +select json '[1]' @* '$[last]'; +select json '{}' @* 'lax $[last]'; +select json '[1,2,3]' @* '$[last]'; +select json '[1,2,3]' @* '$[last - 1]'; +select json '[1,2,3]' @* '$[last ? (@.type() == "number")]'; +select json '[1,2,3]' @* '$[last ? (@.type() == "string")]'; + +select * from jsonpath_query(json '{"a": 10}', '$'); +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)'); +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}'); +select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}'); +select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}'); +select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}'); +select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")'); +select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}'); +select json '[1, "2", null]' @* '$[*] ? (@ != null)'; +select json '[1, "2", null]' @* '$[*] ? (@ == null)'; + +select json '{"a": {"b": 1}}' @* 'lax $.**'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{2}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{2,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,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,}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1,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,}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; + +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; +select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))'; + +--test ternary logic +select + x, y, + jsonpath_query( + json '[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, + jsonpath_query( + json '[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 '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)'; +select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))'; +select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)'; +select json '{"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 '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)'; +select json '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)'; +select json '0' @* '1 / $'; + +-- unwrapping of operator arguments in lax mode +select json '{"a": [2]}' @* 'lax $.a * 3'; +select json '{"a": [2]}' @* 'lax $.a + 3'; +select json '{"a": [2, 3, 4]}' @* 'lax -$.a'; +-- should fail +select json '{"a": [1, 2]}' @* 'lax $.a * 3'; + +-- extension: boolean expressions +select json '2' @* '$ > 1'; +select json '2' @* '$ <= 1'; +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 jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}'); +select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}'); + +select json '[null,1,true,"a",[],{}]' @* '$.type()'; +select json '[null,1,true,"a",[],{}]' @* 'lax $.type()'; +select json '[null,1,true,"a",[],{}]' @* '$[*].type()'; +select json 'null' @* 'null.type()'; +select json 'null' @* 'true.type()'; +select json 'null' @* '123.type()'; +select json 'null' @* '"123".type()'; + +select json '{"a": 2}' @* '($.a - 5).abs() + 10'; +select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10'; +select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)'; +select json '[1, 2, 3]' @* '($[*] > 3).type()'; +select json '[1, 2, 3]' @* '($[*].a > 3).type()'; +select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()'; + +select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()'; +select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()'; + +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()'; +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()'; +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()'; +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()'; +select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()'; + +select json '[{},1]' @* '$[*].keyvalue()'; +select json '{}' @* '$.keyvalue()'; +select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()'; +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()'; +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()'; +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()'; + +select json 'null' @* '$.double()'; +select json 'true' @* '$.double()'; +select json '[]' @* '$.double()'; +select json '[]' @* 'strict $.double()'; +select json '{}' @* '$.double()'; +select json '1.23' @* '$.double()'; +select json '"1.23"' @* '$.double()'; +select json '"1.23aaa"' @* '$.double()'; + +select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")'; +select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")'; +select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")'; +select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")'; +select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)'; +select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")'; +select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)'; +select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)'; + +select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")'; +select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")'; + +select json 'null' @* '$.datetime()'; +select json 'true' @* '$.datetime()'; +select json '[]' @* '$.datetime()'; +select json '[]' @* 'strict $.datetime()'; +select json '{}' @* '$.datetime()'; +select json '""' @* '$.datetime()'; + +select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; +select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()'; + +select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()'; +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()'; +select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()'; +select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()'; + +set time zone '+00'; + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select json '"12:34"' @* '$.datetime("HH24:MI")'; +select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; +select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + +set time zone '+10'; + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; +select json '"12:34"' @* '$.datetime("HH24:MI")'; +select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; +select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; + +set time zone default; + +select json '"2017-03-10"' @* '$.datetime().type()'; +select json '"2017-03-10"' @* '$.datetime()'; +select json '"2017-03-10 12:34:56"' @* '$.datetime().type()'; +select json '"2017-03-10 12:34:56"' @* '$.datetime()'; +select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; +select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; +select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; +select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; +select json '"12:34:56"' @* '$.datetime().type()'; +select json '"12:34:56"' @* '$.datetime()'; +select json '"12:34:56 +3"' @* '$.datetime().type()'; +select json '"12:34:56 +3"' @* '$.datetime()'; +select json '"12:34:56 +3:10"' @* '$.datetime().type()'; +select json '"12:34:56 +3:10"' @* '$.datetime()'; + +set time zone '+00'; + +-- date comparison +select json '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '["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 '[{"a": 1}, {"a": 2}]' @* '$[*]'; +SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; + +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a'; +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; +SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; + +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)'; +SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)'; + +SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1'; +SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; From 905e9b75a3d5fea3293204584eb0d0ca14ae65f6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 03:24:39 +0300 Subject: [PATCH 66/97] Fix jsonpath timestamptz encoding in json --- src/backend/utils/adt/json.c | 3 +- src/test/regress/expected/json_jsonpath.out | 68 ++++++++++++++------- src/test/regress/sql/json_jsonpath.sql | 7 +++ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 034c300826..b19d7b1523 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -2985,7 +2985,8 @@ JsonEncodeJsonbValue(StringInfo buf, JsonbValue *jbv) JsonEncodeDateTime(dtbuf, jbv->val.datetime.value, - jbv->val.datetime.typid); + jbv->val.datetime.typid, + &jbv->val.datetime.tz); escape_json(buf, dtbuf); diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index d7c63dcbde..abe5f004ff 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -1270,33 +1270,49 @@ select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; (1 row) select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'; ?column? ----------------------------- "2017-03-10T12:34:00+00:00" (1 row) +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'; + ?column? +----------------------------- + "2017-03-10T12:34:00+00:12" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'; + ?column? +-------------------------------- + "2017-03-10T12:34:00-00:12:34" +(1 row) + +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'; +ERROR: Invalid argument for SQL/JSON datetime function select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T07:34:00+00:00" + "2017-03-10T12:34:00+05:00" (1 row) select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T17:34:00+00:00" + "2017-03-10T12:34:00-05:00" (1 row) select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T07:14:00+00:00" + "2017-03-10T12:34:00+05:20" (1 row) select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T17:54:00+00:00" + "2017-03-10T12:34:00-05:20" (1 row) select json '"12:34"' @* '$.datetime("HH24:MI")'; @@ -1306,6 +1322,8 @@ select json '"12:34"' @* '$.datetime("HH24:MI")'; (1 row) select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '"12:34"' @* '$.datetime("HH24:MI TZH", "+00")'; ?column? ------------------ "12:34:00+00:00" @@ -1343,6 +1361,8 @@ select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; (1 row) select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'; ?column? ----------------------------- "2017-03-10T12:34:00+10:00" @@ -1351,25 +1371,25 @@ select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH") select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-10T17:34:00+10:00" + "2017-03-10T12:34:00+05:00" (1 row) select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; ?column? ----------------------------- - "2017-03-11T03:34:00+10:00" + "2017-03-10T12:34:00-05:00" (1 row) select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-10T17:14:00+10:00" + "2017-03-10T12:34:00+05:20" (1 row) select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; ?column? ----------------------------- - "2017-03-11T03:54:00+10:00" + "2017-03-10T12:34:00-05:20" (1 row) select json '"12:34"' @* '$.datetime("HH24:MI")'; @@ -1379,6 +1399,8 @@ select json '"12:34"' @* '$.datetime("HH24:MI")'; (1 row) select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +ERROR: Invalid argument for SQL/JSON datetime function +select json '"12:34"' @* '$.datetime("HH24:MI TZH", "+10")'; ?column? ------------------ "12:34:00+10:00" @@ -1442,7 +1464,7 @@ select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()'; select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()'; ?column? ----------------------------- - "2017-03-10T01:34:56-08:00" + "2017-03-10T12:34:56+03:00" (1 row) select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; @@ -1454,7 +1476,7 @@ select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()'; select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()'; ?column? ----------------------------- - "2017-03-10T01:24:56-08:00" + "2017-03-10T12:34:56+03:10" (1 row) select json '"12:34:56"' @* '$.datetime().type()'; @@ -1501,7 +1523,7 @@ select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +0 ----------------------------- "2017-03-10" "2017-03-10T00:00:00" - "2017-03-10T00:00:00+00:00" + "2017-03-10T03:00:00+03:00" (3 rows) select json '["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"]' @@ -1512,7 +1534,7 @@ select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +0 "2017-03-11" "2017-03-10T00:00:00" "2017-03-10T12:34:56" - "2017-03-10T00:00:00+00:00" + "2017-03-10T03:00:00+03:00" (5 rows) select json '["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"]' @@ -1520,7 +1542,7 @@ select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +0 ?column? ----------------------------- "2017-03-09" - "2017-03-09T21:02:03+00:00" + "2017-03-10T01:02:03+04:00" (2 rows) -- time comparison @@ -1584,7 +1606,7 @@ select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00 ?column? ----------------------------- "2017-03-10T12:35:00" - "2017-03-10T12:35:00+00:00" + "2017-03-10T13:35:00+01:00" (2 rows) select json '["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"]' @@ -1593,8 +1615,8 @@ select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00 ----------------------------- "2017-03-10T12:35:00" "2017-03-10T12:36:00" - "2017-03-10T12:35:00+00:00" - "2017-03-10T13:35:00+00:00" + "2017-03-10T13:35:00+01:00" + "2017-03-10T12:35:00-01:00" "2017-03-11" (5 rows) @@ -1603,7 +1625,7 @@ select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00 ?column? ----------------------------- "2017-03-10T12:34:00" - "2017-03-10T11:35:00+00:00" + "2017-03-10T12:35:00+01:00" "2017-03-10" (3 rows) @@ -1612,7 +1634,7 @@ select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 @* '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:35:00+00:00" + "2017-03-10T12:35:00+01:00" "2017-03-10T11:35:00" (2 rows) @@ -1620,9 +1642,9 @@ select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 @* '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:35:00+00:00" - "2017-03-10T11:36:00+00:00" - "2017-03-10T14:35:00+00:00" + "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" @@ -1632,8 +1654,8 @@ select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 @* '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))'; ?column? ----------------------------- - "2017-03-10T11:34:00+00:00" - "2017-03-10T10:35:00+00:00" + "2017-03-10T12:34:00+01:00" + "2017-03-10T12:35:00+02:00" "2017-03-10T10:35:00" "2017-03-10" (4 rows) diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql index 1072165f48..7504e14d71 100644 --- a/src/test/regress/sql/json_jsonpath.sql +++ b/src/test/regress/sql/json_jsonpath.sql @@ -267,12 +267,17 @@ set time zone '+00'; select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")'; select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select json '"12:34"' @* '$.datetime("HH24:MI")'; select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34"' @* '$.datetime("HH24:MI TZH", "+00")'; select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; @@ -282,12 +287,14 @@ set time zone '+10'; select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")'; select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; +select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")'; select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")'; select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")'; select json '"12:34"' @* '$.datetime("HH24:MI")'; select json '"12:34"' @* '$.datetime("HH24:MI TZH")'; +select json '"12:34"' @* '$.datetime("HH24:MI TZH", "+10")'; select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")'; select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")'; select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")'; From ba11affa5c541f57b3cb56572ec43f4ca0c4be02 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 8 Mar 2018 01:38:40 +0300 Subject: [PATCH 67/97] Change syntax of jsonpath .** accessor in json tests --- src/test/regress/expected/json_jsonpath.out | 48 +++++++++++++-------- src/test/regress/sql/json_jsonpath.sql | 36 ++++++++-------- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index abe5f004ff..09d7df0c11 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -426,13 +426,27 @@ select json '{"a": {"b": 1}}' @* 'lax $.**'; 1 (3 rows) +select json '{"a": {"b": 1}}' @* 'lax $.**{0}'; + ?column? +----------------- + {"a": {"b": 1}} +(1 row) + +select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}'; + ?column? +----------------- + {"a": {"b": 1}} + {"b": 1} + 1 +(3 rows) + select json '{"a": {"b": 1}}' @* 'lax $.**{1}'; ?column? ---------- {"b": 1} (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}'; ?column? ---------- {"b": 1} @@ -445,13 +459,13 @@ select json '{"a": {"b": 1}}' @* 'lax $.**{2}'; 1 (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{2,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}'; ?column? ---------- 1 (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}'; ?column? ---------- (0 rows) @@ -473,19 +487,19 @@ select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; 1 (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; ?column? ---------- 1 @@ -507,25 +521,25 @@ select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; ---------- (0 rows) -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; ?column? ---------- 1 (1 row) -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)'; ?column? ---------- 1 @@ -549,19 +563,19 @@ select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)'; t (1 row) -select json '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select json '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; ?column? ---------- t @@ -585,25 +599,25 @@ select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)'; f (1 row) -select json '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)'; ?column? ---------- t (1 row) -select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)'; ?column? ---------- t diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql index 7504e14d71..22428ccc5b 100644 --- a/src/test/regress/sql/json_jsonpath.sql +++ b/src/test/regress/sql/json_jsonpath.sql @@ -77,38 +77,40 @@ select json '[1, "2", null]' @* '$[*] ? (@ != null)'; select json '[1, "2", null]' @* '$[*] ? (@ == null)'; select json '{"a": {"b": 1}}' @* 'lax $.**'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}'; select json '{"a": {"b": 1}}' @* 'lax $.**{1}'; -select json '{"a": {"b": 1}}' @* 'lax $.**{1,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}'; select json '{"a": {"b": 1}}' @* 'lax $.**{2}'; -select json '{"a": {"b": 1}}' @* 'lax $.**{2,}'; -select json '{"a": {"b": 1}}' @* 'lax $.**{3,}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}'; +select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}'; select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)'; select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)'; select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)'; -select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)'; -select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)'; -select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; +select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)'; select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)'; select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)'; +select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)'; +select json '{"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,}.b ? ( @ > 0)'; -select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)'; -select json '{"a": {"b": 1}}' @? '$.**{1,2}.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,}.b ? ( @ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)'; -select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.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 '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))'; select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))'; From 4d973f9bcca6d42dc8ddeb6937193c2cdc4e2cbe Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 27 Mar 2018 16:24:57 +0300 Subject: [PATCH 68/97] Remove PG_TRY/PG_CATCH in handling of numeric errors inside jsonpath --- src/backend/utils/adt/float.c | 48 +++-- src/backend/utils/adt/jsonpath_exec.c | 107 ++++------ src/backend/utils/adt/numeric.c | 294 +++++++++++++++++--------- src/include/utils/builtins.h | 7 +- src/include/utils/elog.h | 19 ++ src/include/utils/numeric.h | 9 + 6 files changed, 296 insertions(+), 188 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index b86205b098..b9ef7220fd 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -420,7 +420,7 @@ float8in(PG_FUNCTION_ARGS) } /* - * float8in_internal - guts of float8in() + * float8in_internal_safe - guts of float8in() * * This is exposed for use by functions that want a reasonably * platform-independent way of inputting doubles. The behavior is @@ -438,8 +438,8 @@ float8in(PG_FUNCTION_ARGS) * unreasonable amount of extra casting both here and in callers, so we don't. */ double -float8in_internal(char *num, char **endptr_p, - const char *type_name, const char *orig_string) +float8in_internal_safe(char *num, char **endptr_p, const char *type_name, + const char *orig_string, ErrorData **edata) { double val; char *endptr; @@ -453,10 +453,13 @@ float8in_internal(char *num, char **endptr_p, * strtod() on different platforms. */ if (*num == '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - type_name, orig_string))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + return 0; + } errno = 0; val = strtod(num, &endptr); @@ -529,17 +532,21 @@ float8in_internal(char *num, char **endptr_p, char *errnumber = pstrdup(num); errnumber[endptr - num] = '\0'; - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("\"%s\" is out of range for type double precision", - errnumber))); + ereport_safe(edata, ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"%s\" is out of range for type double precision", + errnumber))); + return 0; } } else - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - type_name, orig_string))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + return 0; + } } #ifdef HAVE_BUGGY_SOLARIS_STRTOD else @@ -562,10 +569,13 @@ float8in_internal(char *num, char **endptr_p, if (endptr_p) *endptr_p = endptr; else if (*endptr != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - type_name, orig_string))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + type_name, orig_string))); + return 0; + } return val; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 7433ae39e8..f8944ffbc3 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -910,7 +910,6 @@ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - MemoryContext mcxt = CurrentMemoryContext; JsonPathExecResult jper; JsonPathItem elem; JsonValueList lseq = { 0 }; @@ -919,11 +918,10 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *rval; JsonbValue lvalbuf; JsonbValue rvalbuf; - PGFunction func; - Datum ldatum; - Datum rdatum; - Datum res; + Numeric (*func)(Numeric, Numeric, ErrorData **); + Numeric res; bool hasNext; + ErrorData *edata; jspGetLeftArg(jsp, &elem); @@ -962,25 +960,22 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, if (!found && !hasNext) return jperOk; - ldatum = NumericGetDatum(lval->val.numeric); - rdatum = NumericGetDatum(rval->val.numeric); - switch (jsp->type) { case jpiAdd: - func = numeric_add; + func = numeric_add_internal; break; case jpiSub: - func = numeric_sub; + func = numeric_sub_internal; break; case jpiMul: - func = numeric_mul; + func = numeric_mul_internal; break; case jpiDiv: - func = numeric_div; + func = numeric_div_internal; break; case jpiMod: - func = numeric_mod; + func = numeric_mod_internal; break; default: elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type); @@ -988,29 +983,15 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } - PG_TRY(); - { - res = DirectFunctionCall2(func, ldatum, rdatum); - } - PG_CATCH(); - { - int errcode = geterrcode(); - ErrorData *edata; - - if (ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION) - PG_RE_THROW(); - - MemoryContextSwitchTo(mcxt); - edata = CopyErrorData(); - FlushErrorState(); + edata = NULL; + res = func(lval->val.numeric, rval->val.numeric, &edata); + if (edata) return jperMakeErrorData(edata); - } - PG_END_TRY(); lval = palloc(sizeof(*lval)); lval->type = jbvNumeric; - lval->val.numeric = DatumGetNumeric(res); + lval->val.numeric = res; return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false); } @@ -1947,53 +1928,53 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, case jpiDouble: { JsonbValue jbv; - MemoryContext mcxt = CurrentMemoryContext; + ErrorData *edata = NULL; if (JsonbType(jb) == jbvScalar) jb = JsonbExtractScalar(jb->val.binary.data, &jbv); - PG_TRY(); + if (jb->type == jbvNumeric) { - if (jb->type == jbvNumeric) - { - /* only check success of numeric to double cast */ - DirectFunctionCall1(numeric_float8, - NumericGetDatum(jb->val.numeric)); - res = jperOk; - } - else if (jb->type == jbvString) - { - /* cast string as double */ - char *str = pnstrdup(jb->val.string.val, - jb->val.string.len); - Datum val = DirectFunctionCall1( - float8in, CStringGetDatum(str)); - pfree(str); + /* only check success of numeric to double cast */ + (void) numeric_float8_internal(jb->val.numeric, &edata); + } + else if (jb->type == jbvString) + { + /* cast string as double */ + char *str = pnstrdup(jb->val.string.val, + jb->val.string.len); + double val; + val = float8in_internal_safe(str, NULL, "double precision", + str, &edata); + pfree(str); + + if (!edata) + { jb = &jbv; jb->type = jbvNumeric; - jb->val.numeric = DatumGetNumeric(DirectFunctionCall1( - float8_numeric, val)); - res = jperOk; - + jb->val.numeric = float8_numeric_internal(val, &edata); } - else - res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); } - PG_CATCH(); + else + { + res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); + break; + } + + if (edata) { - if (ERRCODE_TO_CATEGORY(geterrcode()) != - ERRCODE_DATA_EXCEPTION) - PG_RE_THROW(); + if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) != + ERRCODE_DATA_EXCEPTION) + ThrowErrorData(edata); - FlushErrorState(); - MemoryContextSwitchTo(mcxt); + FreeErrorData(edata); res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM); } - PG_END_TRY(); - - if (res == jperOk) + else + { res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); + } } break; case jpiDatetime: diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 074294cbcc..4a2eba3859 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -465,14 +465,15 @@ static void free_var(NumericVar *var); static void zero_var(NumericVar *var); static const char *set_var_from_str(const char *str, const char *cp, - NumericVar *dest); + NumericVar *dest, ErrorData **edata); static void set_var_from_num(Numeric value, NumericVar *dest); static void init_var_from_num(Numeric num, NumericVar *dest); static void set_var_from_var(const NumericVar *value, NumericVar *dest); static char *get_str_from_var(const NumericVar *var); static char *get_str_from_var_sci(const NumericVar *var, int rscale); -static Numeric make_result(const NumericVar *var); +static inline Numeric make_result(const NumericVar *var); +static Numeric make_result_safe(const NumericVar *var, ErrorData **edata); static void apply_typmod(NumericVar *var, int32 typmod); @@ -509,12 +510,12 @@ static void mul_var(const NumericVar *var1, const NumericVar *var2, int rscale); static void div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, - int rscale, bool round); + int rscale, bool round, ErrorData **edata); static void div_var_fast(const NumericVar *var1, const NumericVar *var2, NumericVar *result, int rscale, bool round); static int select_div_scale(const NumericVar *var1, const NumericVar *var2); static void mod_var(const NumericVar *var1, const NumericVar *var2, - NumericVar *result); + NumericVar *result, ErrorData **edata); static void ceil_var(const NumericVar *var, NumericVar *result); static void floor_var(const NumericVar *var, NumericVar *result); @@ -615,7 +616,7 @@ numeric_in(PG_FUNCTION_ARGS) init_var(&value); - cp = set_var_from_str(str, cp, &value); + cp = set_var_from_str(str, cp, &value, NULL); /* * We duplicate a few lines of code here because we would like to @@ -1578,14 +1579,14 @@ compute_bucket(Numeric operand, Numeric bound1, Numeric bound2, sub_var(&operand_var, &bound1_var, &operand_var); sub_var(&bound2_var, &bound1_var, &bound2_var); div_var(&operand_var, &bound2_var, result_var, - select_div_scale(&operand_var, &bound2_var), true); + select_div_scale(&operand_var, &bound2_var), true, NULL); } else { sub_var(&bound1_var, &operand_var, &operand_var); sub_var(&bound1_var, &bound2_var, &bound1_var); div_var(&operand_var, &bound1_var, result_var, - select_div_scale(&operand_var, &bound1_var), true); + select_div_scale(&operand_var, &bound1_var), true, NULL); } mul_var(result_var, count_var, result_var, @@ -2385,17 +2386,9 @@ hash_numeric_extended(PG_FUNCTION_ARGS) * ---------------------------------------------------------------------- */ - -/* - * numeric_add() - - * - * Add two numerics - */ -Datum -numeric_add(PG_FUNCTION_ARGS) +Numeric +numeric_add_internal(Numeric num1, Numeric num2, ErrorData **edata) { - Numeric num1 = PG_GETARG_NUMERIC(0); - Numeric num2 = PG_GETARG_NUMERIC(1); NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2405,7 +2398,7 @@ numeric_add(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); /* * Unpack the values, let add_var() compute the result and return it. @@ -2416,24 +2409,31 @@ numeric_add(PG_FUNCTION_ARGS) init_var(&result); add_var(&arg1, &arg2, &result); - res = make_result(&result); + res = make_result_safe(&result, edata); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } - /* - * numeric_sub() - + * numeric_add() - * - * Subtract one numeric from another + * Add two numerics */ Datum -numeric_sub(PG_FUNCTION_ARGS) +numeric_add(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res = numeric_add_internal(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + +Numeric +numeric_sub_internal(Numeric num1, Numeric num2, ErrorData **edata) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2443,7 +2443,7 @@ numeric_sub(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); /* * Unpack the values, let sub_var() compute the result and return it. @@ -2454,24 +2454,31 @@ numeric_sub(PG_FUNCTION_ARGS) init_var(&result); sub_var(&arg1, &arg2, &result); - res = make_result(&result); + res = make_result_safe(&result, edata); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } - /* - * numeric_mul() - + * numeric_sub() - * - * Calculate the product of two numerics + * Subtract one numeric from another */ Datum -numeric_mul(PG_FUNCTION_ARGS) +numeric_sub(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res = numeric_sub_internal(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + +Numeric +numeric_mul_internal(Numeric num1, Numeric num2, ErrorData **edata) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2481,7 +2488,7 @@ numeric_mul(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); /* * Unpack the values, let mul_var() compute the result and return it. @@ -2496,24 +2503,31 @@ numeric_mul(PG_FUNCTION_ARGS) init_var(&result); mul_var(&arg1, &arg2, &result, arg1.dscale + arg2.dscale); - res = make_result(&result); + res = make_result_safe(&result, edata); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } - /* - * numeric_div() - + * numeric_mul() - * - * Divide one numeric into another + * Calculate the product of two numerics */ Datum -numeric_div(PG_FUNCTION_ARGS) +numeric_mul(PG_FUNCTION_ARGS) { Numeric num1 = PG_GETARG_NUMERIC(0); Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res = numeric_mul_internal(num1, num2, NULL); + + PG_RETURN_NUMERIC(res); +} + +Numeric +numeric_div_internal(Numeric num1, Numeric num2, ErrorData **edata) +{ NumericVar arg1; NumericVar arg2; NumericVar result; @@ -2524,7 +2538,7 @@ numeric_div(PG_FUNCTION_ARGS) * Handle NaN */ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); /* * Unpack the arguments @@ -2542,12 +2556,30 @@ numeric_div(PG_FUNCTION_ARGS) /* * Do the divide and return the result */ - div_var(&arg1, &arg2, &result, rscale, true); + div_var(&arg1, &arg2, &result, rscale, true, edata); - res = make_result(&result); + if (edata && *edata) + res = NULL; /* error occured */ + else + res = make_result_safe(&result, edata); free_var(&result); + return res; +} + +/* + * numeric_div() - + * + * Divide one numeric into another + */ +Datum +numeric_div(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res = numeric_div_internal(num1, num2, NULL); + PG_RETURN_NUMERIC(res); } @@ -2584,7 +2616,7 @@ numeric_div_trunc(PG_FUNCTION_ARGS) /* * Do the divide and return the result */ - div_var(&arg1, &arg2, &result, 0, false); + div_var(&arg1, &arg2, &result, 0, false, NULL); res = make_result(&result); @@ -2593,36 +2625,43 @@ numeric_div_trunc(PG_FUNCTION_ARGS) PG_RETURN_NUMERIC(res); } - -/* - * numeric_mod() - - * - * Calculate the modulo of two numerics - */ -Datum -numeric_mod(PG_FUNCTION_ARGS) +Numeric +numeric_mod_internal(Numeric num1, Numeric num2, ErrorData **edata) { - Numeric num1 = PG_GETARG_NUMERIC(0); - Numeric num2 = PG_GETARG_NUMERIC(1); Numeric res; NumericVar arg1; NumericVar arg2; NumericVar result; if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); init_var_from_num(num1, &arg1); init_var_from_num(num2, &arg2); init_var(&result); - mod_var(&arg1, &arg2, &result); + mod_var(&arg1, &arg2, &result, edata); - res = make_result(&result); + res = make_result_safe(&result, edata); free_var(&result); + return res; +} + +/* + * numeric_mod() - + * + * Calculate the modulo of two numerics + */ +Datum +numeric_mod(PG_FUNCTION_ARGS) +{ + Numeric num1 = PG_GETARG_NUMERIC(0); + Numeric num2 = PG_GETARG_NUMERIC(1); + Numeric res = numeric_mod_internal(num1, num2, NULL); + PG_RETURN_NUMERIC(res); } @@ -3226,55 +3265,73 @@ numeric_int2(PG_FUNCTION_ARGS) } -Datum -float8_numeric(PG_FUNCTION_ARGS) +Numeric +float8_numeric_internal(float8 val, ErrorData **edata) { - float8 val = PG_GETARG_FLOAT8(0); Numeric res; NumericVar result; char buf[DBL_DIG + 100]; if (isnan(val)) - PG_RETURN_NUMERIC(make_result(&const_nan)); + return make_result_safe(&const_nan, edata); if (isinf(val)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot convert infinity to numeric"))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot convert infinity to numeric"))); + return NULL; + } snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val); init_var(&result); /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result); + (void) set_var_from_str(buf, buf, &result, edata); - res = make_result(&result); + res = make_result_safe(&result, edata); free_var(&result); - PG_RETURN_NUMERIC(res); + return res; } - Datum -numeric_float8(PG_FUNCTION_ARGS) +float8_numeric(PG_FUNCTION_ARGS) +{ + float8 val = PG_GETARG_FLOAT8(0); + Numeric res = float8_numeric_internal(val, NULL); + + PG_RETURN_NUMERIC(res); +} + +float8 +numeric_float8_internal(Numeric num, ErrorData **edata) { - Numeric num = PG_GETARG_NUMERIC(0); char *tmp; - Datum result; + float8 result; if (NUMERIC_IS_NAN(num)) - PG_RETURN_FLOAT8(get_float8_nan()); + return get_float8_nan(); tmp = DatumGetCString(DirectFunctionCall1(numeric_out, NumericGetDatum(num))); - result = DirectFunctionCall1(float8in, CStringGetDatum(tmp)); + result = float8in_internal_safe(tmp, NULL, "double precison", tmp, edata); pfree(tmp); - PG_RETURN_DATUM(result); + return result; +} + +Datum +numeric_float8(PG_FUNCTION_ARGS) +{ + Numeric num = PG_GETARG_NUMERIC(0); + float8 result = numeric_float8_internal(num, NULL); + + PG_RETURN_FLOAT8(result); } @@ -3318,7 +3375,7 @@ float4_numeric(PG_FUNCTION_ARGS) init_var(&result); /* Assume we need not worry about leading/trailing spaces */ - (void) set_var_from_str(buf, buf, &result); + (void) set_var_from_str(buf, buf, &result, NULL); res = make_result(&result); @@ -4893,7 +4950,7 @@ numeric_stddev_internal(NumericAggState *state, else mul_var(&vN, &vN, &vNminus1, 0); /* N * N */ rscale = select_div_scale(&vsumX2, &vNminus1); - div_var(&vsumX2, &vNminus1, &vsumX, rscale, true); /* variance */ + div_var(&vsumX2, &vNminus1, &vsumX, rscale, true, NULL); /* variance */ if (!variance) sqrt_var(&vsumX, &vsumX, rscale); /* stddev */ @@ -5619,7 +5676,8 @@ zero_var(NumericVar *var) * reports. (Typically cp would be the same except advanced over spaces.) */ static const char * -set_var_from_str(const char *str, const char *cp, NumericVar *dest) +set_var_from_str(const char *str, const char *cp, NumericVar *dest, + ErrorData **edata) { bool have_dp = false; int i; @@ -5657,10 +5715,13 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest) } if (!isdigit((unsigned char) *cp)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "numeric", str))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); + return NULL; + } decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2); @@ -5681,10 +5742,13 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest) else if (*cp == '.') { if (have_dp) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "numeric", str))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); + return NULL; + } have_dp = true; cp++; } @@ -5705,10 +5769,14 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest) cp++; exponent = strtol(cp, &endptr, 10); if (endptr == cp) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), - errmsg("invalid input syntax for type %s: \"%s\"", - "numeric", str))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for type %s: \"%s\"", + "numeric", str))); + return NULL; + } + cp = endptr; /* @@ -5720,9 +5788,13 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest) * for consistency use the same ereport errcode/text as make_result(). */ if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2)) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value overflows numeric format"))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + return NULL; + } + dweight += (int) exponent; dscale -= (int) exponent; if (dscale < 0) @@ -6064,7 +6136,7 @@ get_str_from_var_sci(const NumericVar *var, int rscale) init_var(&significand); power_var_int(&const_ten, exponent, &denominator, denom_scale); - div_var(var, &denominator, &significand, rscale, true); + div_var(var, &denominator, &significand, rscale, true, NULL); sig_out = get_str_from_var(&significand); free_var(&denominator); @@ -6086,15 +6158,14 @@ get_str_from_var_sci(const NumericVar *var, int rscale) return str; } - /* - * make_result() - + * make_result_safe() - * * Create the packed db numeric format in palloc()'d memory from * a variable. */ static Numeric -make_result(const NumericVar *var) +make_result_safe(const NumericVar *var, ErrorData **edata) { Numeric result; NumericDigit *digits = var->digits; @@ -6165,14 +6236,22 @@ make_result(const NumericVar *var) /* Check for overflow of int16 fields */ if (NUMERIC_WEIGHT(result) != weight || NUMERIC_DSCALE(result) != var->dscale) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("value overflows numeric format"))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value overflows numeric format"))); + return NULL; + } dump_numeric("make_result()", result); return result; } +static inline Numeric +make_result(const NumericVar *var) +{ + return make_result_safe(var, NULL); +} /* * apply_typmod() - @@ -7050,7 +7129,7 @@ mul_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, */ static void div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, - int rscale, bool round) + int rscale, bool round, ErrorData **edata) { int div_ndigits; int res_ndigits; @@ -7075,9 +7154,12 @@ div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, * unnormalized divisor. */ if (var2ndigits == 0 || var2->digits[0] == 0) - ereport(ERROR, - (errcode(ERRCODE_DIVISION_BY_ZERO), - errmsg("division by zero"))); + { + ereport_safe(edata, ERROR, + (errcode(ERRCODE_DIVISION_BY_ZERO), + errmsg("division by zero"))); + return; + } /* * Now result zero check @@ -7698,7 +7780,8 @@ select_div_scale(const NumericVar *var1, const NumericVar *var2) * Calculate the modulo of two numerics at variable level */ static void -mod_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) +mod_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result, + ErrorData **edata) { NumericVar tmp; @@ -7710,7 +7793,10 @@ mod_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result) * div_var can be persuaded to give us trunc(x/y) directly. * ---------- */ - div_var(var1, var2, &tmp, 0, false); + div_var(var1, var2, &tmp, 0, false, edata); + + if (edata && *edata) + return; /* error occured */ mul_var(var2, &tmp, &tmp, var2->dscale); @@ -8363,7 +8449,7 @@ power_var_int(const NumericVar *base, int exp, NumericVar *result, int rscale) round_var(result, rscale); return; case -1: - div_var(&const_one, base, result, rscale, true); + div_var(&const_one, base, result, rscale, true, NULL); return; case 2: mul_var(base, base, result, rscale); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index d0416e90fc..c0f7ce9bdb 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -58,8 +58,11 @@ extern float get_float4_infinity(void); extern double get_float8_nan(void); extern float get_float4_nan(void); extern int is_infinite(double val); -extern double float8in_internal(char *num, char **endptr_p, - const char *type_name, const char *orig_string); +extern double float8in_internal_safe(char *num, char **endptr_p, + const char *type_name, const char *orig_string, + ErrorData **edata); +#define float8in_internal(num, endptr_p, type_name, orig_string) \ + float8in_internal_safe(num, endptr_p, type_name, orig_string, NULL) extern char *float8out_internal(double num); extern int float4_cmp_internal(float4 a, float4 b); extern int float8_cmp_internal(float8 a, float8 b); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 7a9ba7f2ff..6a1527c541 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -124,6 +124,25 @@ #define TEXTDOMAIN NULL +/* + * ereport_safe() -- special macro for copying error info into the specified + * ErrorData **edata (if it is non-NULL) instead of throwing it. This is + * intended for handling of errors of categories like ERRCODE_DATA_EXCEPTION + * without PG_TRY/PG_CATCH, but not for errors like ERRCODE_OUT_OF_MEMORY. + */ +#define ereport_safe(edata, elevel, rest) \ + do { \ + if (edata) { \ + if (errstart(elevel, __FILE__, __LINE__, PG_FUNCNAME_MACRO, TEXTDOMAIN)) { \ + (void)(rest); \ + *(edata) = CopyErrorData(); \ + FlushErrorState(); \ + } \ + } else { \ + ereport(elevel, rest); \ + } \ + } while (0) + extern bool errstart(int elevel, const char *filename, int lineno, const char *funcname, const char *domain); extern void errfinish(int dummy,...); diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index cd8da8bdc2..6e3e3f002b 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -61,4 +61,13 @@ int32 numeric_maximum_size(int32 typmod); extern char *numeric_out_sci(Numeric num, int scale); extern char *numeric_normalize(Numeric num); +/* Functions for safe handling of numeric errors without PG_TRY/PG_CATCH */ +extern Numeric numeric_add_internal(Numeric n1, Numeric n2, ErrorData **edata); +extern Numeric numeric_sub_internal(Numeric n1, Numeric n2, ErrorData **edata); +extern Numeric numeric_mul_internal(Numeric n1, Numeric n2, ErrorData **edata); +extern Numeric numeric_div_internal(Numeric n1, Numeric n2, ErrorData **edata); +extern Numeric numeric_mod_internal(Numeric n1, Numeric n2, ErrorData **edata); +extern Numeric float8_numeric_internal(float8 val, ErrorData **edata); +extern float8 numeric_float8_internal(Numeric num, ErrorData **edata); + #endif /* _PG_NUMERIC_H_ */ From f9767d4f6c47c3284549aa9b0c8ad3a3c8701b8e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 02:18:44 +0300 Subject: [PATCH 69/97] 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 d272719ff4..62f9d8fc15 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -2583,7 +2583,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; @@ -2780,7 +2781,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 065238b0fe..6bc78e8b43 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7496,8 +7496,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 */ @@ -7547,7 +7549,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 */ } @@ -7672,6 +7675,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 * @@ -8052,83 +8074,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; @@ -8681,21 +8658,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; @@ -9027,7 +8993,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 1b4b0d75af..41330b2002 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -437,7 +437,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 315ef82f391dee1a691d0244f54971fbc5d1b8d4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 02:32:55 +0300 Subject: [PATCH 70/97] 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 cc9efab243..758b86f1b7 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2481,11 +2481,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: @@ -2501,8 +2503,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_ArrayRef: @@ -2520,7 +2524,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 1c12075b01..f05adf403e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1432,6 +1432,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; @@ -1471,6 +1473,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; @@ -1512,6 +1516,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 6a971d0141..35c13a5746 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -226,6 +226,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; @@ -258,6 +260,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; @@ -289,6 +293,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 979d523e00..1b4e0ed67d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1207,6 +1207,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); } @@ -1236,6 +1238,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); } @@ -1267,6 +1271,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 42aff7f57a..c59bef3ebd 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -600,6 +600,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(); @@ -639,6 +641,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(); @@ -680,6 +684,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 505ae0af85..69f49a519f 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -2653,6 +2653,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; @@ -2699,6 +2701,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 6bc78e8b43..ad4e3156b2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8974,6 +8974,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 */ @@ -8988,6 +8998,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 @@ -9042,12 +9053,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) { @@ -9057,6 +9075,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, ')'); } @@ -9155,6 +9176,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 "); @@ -9221,6 +9244,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 41330b2002..641500ed9a 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -249,6 +249,11 @@ typedef struct Param int location; /* token location, or -1 if unknown */ } Param; +typedef enum FuncFormat +{ + FUNCFMT_REGULAR = 0, +} FuncFormat; + /* * Aggref * @@ -308,6 +313,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; @@ -361,6 +368,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; @@ -456,6 +465,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 37246b2fe5246c757ea00d2d43c68cb22b60c5d9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Feb 2017 18:41:57 +0300 Subject: [PATCH 71/97] 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 1bd2599c2c..ebc41ea633 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" @@ -628,3 +629,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 90dfac2cb1..a31f8fecf1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -211,6 +211,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JoinType jtype; DropBehavior dbehavior; OnCommitAction oncommit; + JsonFormat jsformat; List *list; Node *node; Value *value; @@ -241,6 +242,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 @@ -585,6 +592,72 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type hash_partbound partbound_datum_list range_datum_list %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 @@ -607,7 +680,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 @@ -617,8 +690,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 @@ -628,12 +701,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 @@ -644,9 +717,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 @@ -658,7 +732,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 @@ -666,17 +740,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 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 STRICT_P STRING STRIP_P SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -684,8 +758,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 @@ -709,11 +783,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 @@ -752,6 +826,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 '+' '-' @@ -776,6 +851,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 + %% /* @@ -12824,7 +12902,7 @@ ConstInterval: opt_timezone: WITH_LA TIME ZONE { $$ = true; } - | WITHOUT TIME ZONE { $$ = false; } + | WITHOUT_LA TIME ZONE { $$ = false; } | /*EMPTY*/ { $$ = false; } ; @@ -13325,6 +13403,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 { /* @@ -13417,6 +13537,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. * @@ -13677,6 +13816,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; } ; @@ -13690,6 +13836,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; } ; /* @@ -13911,6 +14058,8 @@ func_expr_common_subexpr: n->location = @1; $$ = (Node *)n; } + | json_func_expr + { $$ = $1; } ; /* @@ -14599,6 +14748,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; } + ; /***************************************************************************** * @@ -14995,6 +15633,7 @@ ColLabel: IDENT { $$ = $1; } */ unreserved_keyword: ABORT_P + | ABSENT | ABSOLUTE_P | ACCESS | ACTION @@ -15031,6 +15670,7 @@ unreserved_keyword: | COMMENTS | COMMIT | COMMITTED + | CONDITIONAL | CONFIGURATION | CONFLICT | CONNECTION @@ -15066,10 +15706,12 @@ unreserved_keyword: | DOUBLE_P | DROP | EACH + | EMPTY_P | ENABLE_P | ENCODING | ENCRYPTED | ENUM_P + | ERROR_P | ESCAPE | EVENT | EXCLUDE @@ -15115,7 +15757,10 @@ unreserved_keyword: | INSTEAD | INVOKER | ISOLATION + | JSON + | KEEP | KEY + | KEYS | LABEL | LANGUAGE | LARGE_P @@ -15153,6 +15798,7 @@ unreserved_keyword: | OFF | OIDS | OLD + | OMIT | OPERATOR | OPTION | OPTIONS @@ -15182,6 +15828,7 @@ unreserved_keyword: | PROGRAM | PUBLICATION | QUOTE + | QUOTES | RANGE | READ | REASSIGN @@ -15210,6 +15857,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -15258,6 +15906,7 @@ unreserved_keyword: | TYPES_P | UNBOUNDED | UNCOMMITTED + | UNCONDITIONAL | UNENCRYPTED | UNKNOWN | UNLISTEN @@ -15315,6 +15964,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 @@ -15329,6 +15985,7 @@ col_name_keyword: | ROW | SETOF | SMALLINT + | STRING | SUBSTRING | TIME | TIMESTAMP @@ -15366,6 +16023,7 @@ type_func_name_keyword: | CONCURRENTLY | CROSS | CURRENT_SCHEMA + | FORMAT | FREEZE | FULL | ILIKE diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index db30483459..3be9d6c549 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 57bd52ff24..f7aec03b9f 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -88,4 +88,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 43f1552241..01e903e056 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -474,6 +474,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 6390f7e8c1..f9c1bd464a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1428,6 +1428,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 641500ed9a..41b69acd96 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1169,6 +1169,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 23db40147b..7732c1bd49 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -27,6 +27,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) @@ -89,6 +90,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) @@ -142,11 +144,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) @@ -169,6 +173,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) @@ -221,7 +226,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) @@ -277,6 +292,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) @@ -318,6 +334,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) @@ -351,6 +368,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("stdin", STDIN, UNRESERVED_KEYWORD) PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD) PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD) PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD) +PG_KEYWORD("string", STRING, COL_NAME_KEYWORD) PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) @@ -417,6 +436,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 b20383ab17..f0b2416420 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -45,6 +45,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 e5a8f9d170..e576202059 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 c9e0662b5953d724e0b725bce7dd7e52ef744fe9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 11 Feb 2017 03:07:36 +0300 Subject: [PATCH 72/97] 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 758b86f1b7..9578da8260 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2812,6 +2812,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 e284fd71d7..944fc73df5 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2113,6 +2113,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 f05adf403e..721b4dbf21 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2192,6 +2192,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5079,6 +5157,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 35c13a5746..bcabfd6e3c 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -818,6 +818,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 relation.h */ @@ -3166,6 +3191,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 a10014f755..c5510f8773 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; } @@ -903,6 +908,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 */ @@ -1104,6 +1112,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; @@ -1544,6 +1556,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; @@ -2229,6 +2244,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)); @@ -3060,6 +3077,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)); @@ -3704,6 +3731,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 1b4e0ed67d..fb24fa2030 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1767,6 +1767,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 relation.h. @@ -4322,6 +4347,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 c59bef3ebd..2b6f98a031 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1334,6 +1334,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. */ @@ -2747,6 +2782,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 385e54a9b6..343ce85c4b 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -35,6 +35,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" @@ -121,6 +122,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, @@ -369,6 +371,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)); @@ -3485,3 +3491,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 4932e58022..26e26fd338 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1918,6 +1918,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 b19d7b1523..d6840b6bfd 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; @@ -2058,6 +2075,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. * @@ -2192,11 +2303,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; @@ -2205,9 +2314,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(); @@ -2222,19 +2333,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, " : "); @@ -2244,9 +2389,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 9d0582cad0..37244b2c31 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; @@ -1121,11 +1131,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; @@ -1133,9 +1253,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(); @@ -1150,25 +1272,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 ad4e3156b2..10bca087b7 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7674,6 +7674,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 @@ -8808,6 +8850,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; @@ -8979,6 +9030,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; } @@ -8995,6 +9075,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; @@ -9055,12 +9136,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; } @@ -9069,8 +9156,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 a6e4436327..3655bd38d0 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8262,6 +8262,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' }, @@ -9116,6 +9121,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 01e903e056..0b1d0c5b1a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -488,6 +488,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 f9c1bd464a..8977630953 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1628,6 +1628,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 41b69acd96..f218c989bf 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -252,6 +252,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 2e0cd2df8f..82c52fa9d2 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,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 json_jsonpath jsonb_jsonpath +test: json jsonb json_encoding jsonpath 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 494ccebe73..c9f8c44b6c 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -163,6 +163,7 @@ test: json_encoding test: jsonpath test: json_jsonpath test: jsonb_jsonpath +test: sqljson test: indirect_toast test: equivclass test: plancache 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 40fa70fb8895cc80f8ca7fc9e2e6ba7893b262c3 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 12:10:04 +0300 Subject: [PATCH 73/97] 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 721b4dbf21..91a6422241 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2270,6 +2270,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5172,6 +5188,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 c5510f8773..3e92ebaa66 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3755,6 +3755,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 343ce85c4b..baec3669d0 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -123,6 +123,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, @@ -375,6 +376,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)); @@ -3864,3 +3869,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 26e26fd338..98df9bc18c 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1921,6 +1921,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 d6840b6bfd..87c8813e38 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -2423,11 +2423,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; @@ -2438,7 +2436,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(); @@ -2449,6 +2448,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); @@ -2459,6 +2461,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 37244b2c31..78fa74e967 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -1350,11 +1350,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; @@ -1364,7 +1362,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(); @@ -1374,13 +1373,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 10bca087b7..73ba414b29 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9141,6 +9141,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 3655bd38d0..d9312cb756 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8252,6 +8252,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', @@ -9111,6 +9116,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 cb6ae6741c657cefe970477c3b3b90349ded0cda Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 23:29:46 +0300 Subject: [PATCH 74/97] Add JSON_OBJECTAGG() and JSON_ARRAYAGG() transformation --- src/backend/nodes/copyfuncs.c | 45 +++++++ 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 | 38 ++++++ src/test/regress/expected/sqljson.out | 157 +++++++++++++++++++++++ src/test/regress/sql/sqljson.sql | 87 +++++++++++++ 11 files changed, 770 insertions(+), 33 deletions(-) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 91a6422241..65233d2e82 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2256,6 +2256,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 */ @@ -2286,6 +2306,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5185,12 +5224,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 3e92ebaa66..8682c7ac9a 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3765,6 +3765,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 baec3669d0..aef63cbc5f 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" @@ -124,6 +126,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, @@ -380,6 +384,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)); @@ -3870,6 +3882,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 98df9bc18c..107bbaabb0 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1924,6 +1924,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 87c8813e38..0cfea9ff68 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); @@ -1979,8 +1980,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; @@ -2020,9 +2021,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)) { @@ -2034,7 +2040,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)) { @@ -2052,6 +2058,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 */ @@ -2174,13 +2199,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)) { @@ -2201,6 +2229,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); @@ -2228,7 +2260,6 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) else { state = (JsonAggState *) PG_GETARG_POINTER(0); - appendStringInfoString(state->str, ", "); } /* @@ -2244,11 +2275,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)) @@ -2262,6 +2323,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. */ @@ -2279,6 +2360,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 78fa74e967..df217593ee 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); @@ -1654,12 +1655,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; @@ -1707,6 +1704,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); @@ -1776,6 +1776,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) { @@ -1808,11 +1826,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; @@ -1826,6 +1842,7 @@ jsonb_object_agg_transfn(PG_FUNCTION_ARGS) *jbval; JsonbValue v; JsonbIteratorToken type; + bool skip; if (!AggCheckCallContext(fcinfo, &aggcontext)) { @@ -1845,6 +1862,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); @@ -1880,6 +1902,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)); @@ -1935,6 +1966,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; @@ -2007,6 +2050,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 73ba414b29..85a13f20ff 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9193,8 +9193,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 @@ -9223,13 +9224,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)) @@ -9265,7 +9277,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); @@ -9319,6 +9341,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, @@ -9336,16 +9359,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 d2a4298569..954ec8a43b 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -541,14 +541,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 d9312cb756..3db67adc82 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8224,17 +8224,31 @@ proname => 'json_agg_transfn', proisstrict => 'f', provolatile => 's', prorettype => 'internal', proargtypes => 'internal anyelement', prosrc => 'json_agg_transfn' }, +{ oid => '3426', 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' }, +#define F_JSON_AGG 3175 { oid => '3175', descr => 'aggregate input into json', proname => 'json_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +#define F_JSON_AGG_STRICT 3450 +{ oid => '3424', 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 => '3427', 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', @@ -8243,6 +8257,11 @@ proname => 'json_object_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'json', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +#define F_JSON_OBJECTAGG 3451 +{ oid => '3425', 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', @@ -9087,18 +9106,32 @@ 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', prosrc => 'jsonb_agg_finalfn' }, +#define F_JSONB_AGG 3267 { oid => '3267', descr => 'aggregate input into jsonb', proname => 'jsonb_agg', prokind => 'a', proisstrict => 'f', provolatile => 's', prorettype => 'jsonb', proargtypes => 'anyelement', prosrc => 'aggregate_dummy' }, +#define F_JSONB_AGG_STRICT 6063 +{ 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 => '3423', 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', @@ -9107,6 +9140,11 @@ proname => 'jsonb_object_agg', prokind => 'a', proisstrict => 'f', prorettype => 'jsonb', proargtypes => 'any any', prosrc => 'aggregate_dummy' }, +#define F_JSONB_OBJECTAGG 6064 +{ 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/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/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 df1bf8f7f5d4406d01da230a071a8806e2f972be Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 13 Feb 2017 23:32:41 +0300 Subject: [PATCH 75/97] 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 65233d2e82..4f09704898 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2325,6 +2325,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5233,6 +5250,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 8682c7ac9a..91c41df68d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3797,6 +3797,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 aef63cbc5f..0e59df1f92 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -126,6 +126,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, @@ -384,6 +386,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; @@ -3882,6 +3888,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 107bbaabb0..9274d5c625 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1922,6 +1922,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 b064ff11904cf2c45b89d7c9a5bb26beda461f8e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Nov 2017 03:47:28 +0300 Subject: [PATCH 76/97] 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 564dc5275e839598b23e015ba3cd15dfc81a753f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Feb 2017 18:45:43 +0300 Subject: [PATCH 77/97] 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 9578da8260..4230e2bdca 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2833,6 +2833,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 4f09704898..5b90e294b3 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2342,6 +2342,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5256,6 +5286,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 bcabfd6e3c..1f8a84246f 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -843,6 +843,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 relation.h */ @@ -3197,6 +3207,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 91c41df68d..0c1bc6b493 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3807,6 +3807,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 fb24fa2030..84e2ec7023 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1792,6 +1792,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 relation.h. @@ -4353,6 +4362,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 2b6f98a031..981737e04b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1369,6 +1369,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. */ @@ -2786,6 +2800,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 0e59df1f92..467620b6da 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -130,6 +130,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, @@ -398,6 +399,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)); @@ -4174,3 +4179,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 0cfea9ff68..4f9da9727d 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, @@ -2793,6 +2808,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 df217593ee..eaf80ff5e1 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2104,6 +2104,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 85a13f20ff..300f4e383a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9059,6 +9059,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; } @@ -9076,6 +9107,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; @@ -9148,6 +9180,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, @@ -9158,11 +9197,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; @@ -9181,7 +9226,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 3db67adc82..0ad62ec7a3 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8303,6 +8303,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 => '6060', descr => 'check json value type and key uniqueness', + proname => 'json_is_valid', prorettype => 'bool', + proargtypes => 'json text bool', prosrc => 'json_is_valid' }, +{ oid => '6061', + 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', @@ -9177,6 +9184,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 0b1d0c5b1a..5d70dedf95 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -489,6 +489,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 8977630953..0b6e6ff407 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1537,6 +1537,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 f218c989bf..1d2bcc81c9 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -255,7 +255,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 dae99a0479..b8a75ee2a8 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -206,11 +206,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 fc4725002b20a30783539693c97ca4d98496927f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Feb 2017 18:45:43 +0300 Subject: [PATCH 78/97] Add JSON_VALUE, JSON_EXISTS, JSON_QUERY --- .../pg_stat_statements/pg_stat_statements.c | 12 + src/backend/executor/execExpr.c | 90 ++ src/backend/executor/execExprInterp.c | 397 +++++++++ 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 | 101 +++ src/backend/utils/adt/ruleutils.c | 119 +++ 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 | 19 +- src/test/regress/expected/json_sqljson.out | 15 + src/test/regress/expected/jsonb_sqljson.out | 823 ++++++++++++++++++ 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 ++++++ 27 files changed, 3059 insertions(+), 13 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 4230e2bdca..45dc34f502 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2841,6 +2841,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 944fc73df5..03a190079f 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -45,6 +45,7 @@ #include "pgstat.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/jsonpath.h" #include "utils/lsyscache.h" #include "utils/typcache.h" @@ -2118,6 +2119,95 @@ 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->var.varName = cstring_to_text(argname->val.str); + var->var.typid = exprType((Node *) argexpr); + var->var.typmod = exprTypmod((Node *) argexpr); + var->var.cb = EvalJsonPathVar; + var->var.cb_arg = var; + 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 9d6e25aae5..ce817a2aa5 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" @@ -384,6 +388,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, @@ -1748,7 +1753,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(); } @@ -4125,3 +4136,389 @@ 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. + */ +Datum +EvalJsonPathVar(void *cxt, bool *isnull) +{ + JsonPathVariableEvalContext *ecxt = cxt; + + if (!ecxt->evaluated) + { + ecxt->value = ExecEvalExpr(ecxt->estate, ecxt->econtext, &ecxt->isnull); + ecxt->evaluated = true; + } + + *isnull = ecxt->isnull; + return ecxt->value; +} + +/* + * Prepare SQL/JSON item coercion to the output type. Returned a datum of the + * corresponding SQL type and a pointer to the coercion state. + */ +Datum +ExecPrepareJsonItemCoercion(JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pcoercion) +{ + struct JsonCoercionState *coercion; + Datum res; + JsonbValue jbvbuf; + + if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) + item = JsonbExtractScalar(item->val.binary.data, &jbvbuf); + + /* get coercion state reference and datum of the corresponding SQL type */ + switch (item->type) + { + case jbvNull: + coercion = &coercions->null; + res = (Datum) 0; + break; + + case jbvString: + coercion = &coercions->string; + res = PointerGetDatum( + cstring_to_text_with_len(item->val.string.val, + item->val.string.len)); + break; + + case jbvNumeric: + coercion = &coercions->numeric; + res = NumericGetDatum(item->val.numeric); + break; + + case jbvBool: + coercion = &coercions->boolean; + res = BoolGetDatum(item->val.boolean); + break; + + case jbvDatetime: + res = item->val.datetime.value; + switch (item->val.datetime.typid) + { + case DATEOID: + coercion = &coercions->date; + break; + case TIMEOID: + coercion = &coercions->time; + break; + case TIMETZOID: + coercion = &coercions->timetz; + break; + case TIMESTAMPOID: + coercion = &coercions->timestamp; + break; + case TIMESTAMPTZOID: + coercion = &coercions->timestamptz; + break; + default: + elog(ERROR, "unexpected jsonb datetime type oid %d", + item->val.datetime.typid); + return (Datum) 0; + } + break; + + case jbvArray: + case jbvObject: + case jbvBinary: + coercion = &coercions->composite; + res = JsonbPGetDatum(JsonbValueToJsonb(item)); + break; + + default: + elog(ERROR, "unexpected jsonb value type %d", item->type); + return (Datum) 0; + } + + *pcoercion = coercion; + + return res; +} + +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: + { + JsonbValue *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(JsonbValueToJsonb(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 36c5f7d500..401f5690f9 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -2505,6 +2505,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 5b90e294b3..2e62c4702e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2342,6 +2342,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 */ @@ -2372,6 +2460,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5292,6 +5425,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 1f8a84246f..cd3ee22840 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -853,6 +853,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 relation.h */ @@ -3210,6 +3277,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 0c1bc6b493..c750261006 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; } @@ -911,6 +921,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 */ @@ -1116,6 +1141,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; @@ -1559,6 +1599,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; @@ -2246,6 +2295,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)); @@ -3087,6 +3185,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)); @@ -3809,6 +3955,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 84e2ec7023..9529717bd5 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1792,6 +1792,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) { @@ -4365,6 +4423,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 981737e04b..6954854dd9 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1357,7 +1357,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); @@ -1369,6 +1368,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 */ @@ -2802,6 +2874,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 a2a7e0c520..e16a1790e5 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3912,7 +3912,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 6d34245083..e486e7c254 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 467620b6da..0e5f0fd561 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -131,6 +131,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, @@ -403,6 +405,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)); @@ -3576,13 +3586,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; @@ -3599,6 +3622,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) @@ -3616,6 +3650,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 @@ -3627,7 +3691,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 ? @@ -3674,6 +3738,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. */ @@ -3861,8 +3943,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); @@ -4057,7 +4138,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, @@ -4099,7 +4180,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) { @@ -4150,8 +4231,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); } @@ -4284,3 +4364,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 9274d5c625..7a4351541d 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1931,6 +1931,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 eaf80ff5e1..6f209462b8 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -2336,3 +2336,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 f8944ffbc3..a252383857 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2712,3 +2712,104 @@ wrapItemsInArray(const JsonValueList *items) return pushJsonbValue(&ps, WJB_END_ARRAY, NULL); } + +/********************Interface to pgsql's executor***************************/ +bool +JsonbPathExists(Datum jb, JsonPath *jp, List *vars) +{ + JsonPathExecResult res = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + NULL); + + throwJsonPathError(res); + + return res == jperOk; +} + +Datum +JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars) +{ + JsonbValue *first; + bool wrap; + JsonValueList found = { 0 }; + JsonPathExecResult jper = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + &found); + int count; + + throwJsonPathError(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 || + IsAJsonbScalar(first) || + (first->type == jbvBinary && + JsonContainerIsScalar(first->val.binary.data)); + else + { + elog(ERROR, "unrecognized json wrapper %d", wrapper); + wrap = false; + } + + if (wrap) + return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found))); + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), + errmsg("more than one SQL/JSON item"))); + + if (first) + return JsonbPGetDatum(JsonbValueToJsonb(first)); + + *empty = true; + return PointerGetDatum(NULL); +} + +JsonbValue * +JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) +{ + JsonbValue *res; + JsonValueList found = { 0 }; + JsonPathExecResult jper = executeJsonPath(jp, vars, DatumGetJsonbP(jb), + &found); + int count; + + throwJsonPathError(jper); + + count = JsonValueListLength(&found); + + *empty = !count; + + if (*empty) + return NULL; + + if (count > 1) + ereport(ERROR, + (errcode(ERRCODE_MORE_THAN_ONE_JSON_ITEM), + errmsg("more than one SQL/JSON item"))); + + res = JsonValueListHead(&found); + + if (res->type == jbvBinary && + JsonContainerIsScalar(res->val.binary.data)) + JsonbExtractScalar(res->val.binary.data, res); + + if (!IsAJsonbScalar(res)) + ereport(ERROR, + (errcode(ERRCODE_JSON_SCALAR_REQUIRED), + errmsg("SQL/JSON scalar required"))); + + if (res->type == jbvNull) + return NULL; + + return res; +} diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 300f4e383a..70db932478 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7395,6 +7395,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; @@ -7513,6 +7514,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; @@ -7736,6 +7738,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 * @@ -8850,6 +8900,7 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_JsonValueExpr: { JsonValueExpr *jve = (JsonValueExpr *) node; @@ -8859,6 +8910,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; @@ -8955,6 +9073,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/executor/execExpr.h b/src/include/executor/execExpr.h index f7b1f77616..a848c84ebb 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 ArrayRefState; +struct JsonbValue; /* 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, @@ -636,6 +638,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; @@ -735,6 +781,12 @@ extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, + JsonReturning *returning, + struct JsonCoercionsState *coercions, + struct JsonCoercionState **pjcstate); extern 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 5d70dedf95..4527b5e759 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -195,6 +195,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 1d2bcc81c9..0888477f87 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1279,6 +1279,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 2ea1ec1ac8..d312b6cd6c 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -401,6 +401,9 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in, extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in, int estimated_len); +extern Jsonb *JsonbMakeEmptyArray(void); +extern Jsonb *JsonbMakeEmptyObject(void); +extern char *JsonbUnquote(Jsonb *jb); extern JsonbValue *JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res); #endif /* __JSONB_H__ */ diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index c808fb1881..c349e10802 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -17,6 +17,7 @@ #include "fmgr.h" #include "utils/jsonb.h" #include "nodes/pg_list.h" +#include "nodes/primnodes.h" typedef struct { @@ -276,7 +277,15 @@ typedef struct JsonPathVariable { void *cb_arg; } JsonPathVariable; - +typedef struct JsonPathVariableEvalContext +{ + JsonPathVariable var; + struct ExprContext *econtext; + struct ExprState *estate; + Datum value; + bool isnull; + bool evaluated; +} JsonPathVariableEvalContext; typedef struct JsonValueList { @@ -289,4 +298,12 @@ JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *json, JsonValueList *foundJson); +extern bool JsonbPathExists(Datum jb, JsonPath *path, List *vars); +extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars); +extern JsonbValue *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, + List *vars); + +extern Datum EvalJsonPathVar(void *cxt, bool *isnull); + #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..8a717f0ea8 --- /dev/null +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -0,0 +1,823 @@ +-- 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: SQL/JSON member not found +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 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 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: SQL/JSON scalar required +SELECT JSON_VALUE(jsonb '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '{}', '$' ERROR ON ERROR); +ERROR: SQL/JSON scalar required +SELECT JSON_VALUE(jsonb '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(jsonb '1', 'strict $.a' ERROR ON ERROR); +ERROR: SQL/JSON member not found +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: more than one SQL/JSON 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 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: more than one SQL/JSON item +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 adsrc FROM pg_attrdef WHERE adrelid = 'test_jsonb_constraints'::regclass; + adsrc +------------------------------------------------------------------------------------------------------------ + 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 82c52fa9d2..498542ed19 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,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 json_jsonpath jsonb_jsonpath sqljson +test: json jsonb json_encoding jsonpath json_jsonpath jsonb_jsonpath jsonb_sqljson sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index c9f8c44b6c..452c2145a6 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -164,6 +164,8 @@ test: jsonpath test: json_jsonpath test: jsonb_jsonpath test: sqljson +test: json_sqljson +test: jsonb_sqljson test: indirect_toast test: equivclass test: plancache 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..f89c9243a1 --- /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 adsrc 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 f51fb8f6b951273fa786dfd72653dfc5a1f277bb Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 11 May 2017 18:37:18 +0300 Subject: [PATCH 79/97] 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 03a190079f..81a28214da 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2187,6 +2187,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 ce817a2aa5..3a64189fa0 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" @@ -4232,6 +4233,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 e358b5ad13..0c37932ae8 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -3051,6 +3051,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 a848c84ebb..686f1647ab 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -662,6 +662,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 6ef601f061..f410bd9991 100644 --- a/src/include/utils/jsonapi.h +++ b/src/include/utils/jsonapi.h @@ -206,6 +206,10 @@ extern text *transform_json_string_values(text *json, void *action_state, extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid, int *tz); +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 8a717f0ea8..a45f5c64cd 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -751,6 +751,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 f89c9243a1..59c6ae9602 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 6f4a00b067cd31045d54f14c299f64e196a80a2f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 3 Nov 2017 14:15:51 +0300 Subject: [PATCH 80/97] 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 | 35 +++++++++++++++++++ src/test/regress/sql/jsonb_sqljson.sql | 9 +++++ 13 files changed, 95 insertions(+), 18 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 45dc34f502..1f0e58178e 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2847,7 +2847,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 81a28214da..390b9b4e64 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2135,6 +2135,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 3a64189fa0..b6ba35aed2 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4464,7 +4464,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); @@ -4476,8 +4476,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 2e62c4702e..8f51a0e661 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2483,7 +2483,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 c750261006..f699977340 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3192,6 +3192,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 *); @@ -3963,6 +3964,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 a31f8fecf1..13ab62798d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -612,6 +612,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 @@ -621,8 +622,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 @@ -827,6 +827,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 '+' '-' @@ -14804,7 +14805,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 0e5f0fd561..9c666368e8 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4414,7 +4414,7 @@ static JsonExpr * transformJsonExprCommon(ParseState *pstate, JsonFuncExpr *func) { JsonExpr *jsexpr = makeNode(JsonExpr); - Datum jsonpath; + Node *pathspec; JsonFormatType format; if (func->common->pathname) @@ -4445,12 +4445,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 70db932478..d6776a04c9 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -467,6 +467,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 ") @@ -7676,6 +7678,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 */ @@ -8933,7 +8948,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 686f1647ab..80565edb11 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -654,7 +654,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 0b6e6ff407..857f0b5d26 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1488,7 +1488,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 0888477f87..4bfa016db9 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1324,7 +1324,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 a45f5c64cd..c78096cf41 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -868,3 +868,38 @@ 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: bad jsonpath representation +DETAIL: syntax error, unexpected IDENT_P at or near " " diff --git a/src/test/regress/sql/jsonb_sqljson.sql b/src/test/regress/sql/jsonb_sqljson.sql index 59c6ae9602..bdb0f41b48 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 ff2645a7d368a27f2c39a35c1c684aa298087ff4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 30 Nov 2017 19:02:53 +0300 Subject: [PATCH 81/97] Add subtransactions for JsonExpr execution --- src/backend/executor/execExprInterp.c | 42 ++++++++++++++++++++- 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, 105 insertions(+), 2 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index b6ba35aed2..aec089a102 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" @@ -4448,6 +4451,12 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, return res; } +bool +ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr) +{ + return jsexpr->on_error.btype != JSON_BEHAVIOR_ERROR; +} + /* ---------------------------------------------------------------- * ExecEvalJson * ---------------------------------------------------------------- @@ -4487,7 +4496,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, @@ -4495,12 +4504,35 @@ 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; + ExprContext *newecontext; + + BeginInternalSubTransaction(NULL); + /* Want to execute expressions inside function's memory context */ + MemoryContextSwitchTo(oldcontext); + /* + * We need to execute expressions with a new econtext + * that belongs to the current subtransaction; if we try to use + * the outer econtext then ExprContext shutdown callbacks will be + * called at the wrong times. + */ + newecontext = CreateExprContext(econtext->ecxt_estate); PG_TRY(); { - res = ExecEvalJsonExpr(state, op, econtext, jexpr, path, item, + res = ExecEvalJsonExpr(state, op, newecontext, jexpr, path, item, op->resnull); + + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + FreeExprContext(newecontext, true); } PG_CATCH(); { @@ -4511,6 +4543,12 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) edata = CopyErrorData(); FlushErrorState(); + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + FreeExprContext(newecontext, false); + 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 69f49a519f..84bf694ebe 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" @@ -1288,6 +1289,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 80565edb11..ac5da1884e 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -790,6 +790,7 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *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 c78096cf41..30b558b18d 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); SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); ERROR: bad jsonpath representation DETAIL: syntax error, unexpected IDENT_P at or near " " +-- 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 bdb0f41b48..3d58bf6589 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 da82c98b36939c0f82f83a0f5dcc2e8bb9633507 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 15 Mar 2018 01:46:34 +0300 Subject: [PATCH 82/97] Remove ExecExprPassingCaseValue() --- src/backend/executor/execExpr.c | 130 ++++++++++++++------------ src/backend/executor/execExprInterp.c | 56 +++-------- src/include/executor/execExpr.h | 2 + src/include/executor/executor.h | 2 + 4 files changed, 90 insertions(+), 100 deletions(-) diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 390b9b4e64..5b84312b85 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -81,6 +81,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 * @@ -119,32 +153,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); } /* @@ -156,32 +165,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); } /* @@ -2129,7 +2126,7 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.d.jsonexpr.jsexpr = jexpr; scratch.d.jsonexpr.raw_expr = - palloc(sizeof(*scratch.d.jsonexpr.raw_expr)); + palloc(sizeof(*scratch.d.jsonexpr.raw_expr)); ExecInitExprRec((Expr *) jexpr->raw_expr, state, &scratch.d.jsonexpr.raw_expr->value, @@ -2143,12 +2140,20 @@ 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 = @@ -2200,6 +2205,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; @@ -2208,8 +2221,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 aec089a102..c58b5d98cd 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4141,40 +4141,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. */ @@ -4234,8 +4200,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, @@ -4362,8 +4332,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 */ @@ -4410,10 +4382,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 ac5da1884e..3fb1975679 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -655,6 +655,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 f82b51667f..56c00c4931 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -243,6 +243,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 ffd61e29b72e89eea1bddaa5076d17aeb459dc88 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 03:19:46 +0300 Subject: [PATCH 83/97] 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 30b558b18d..c516870004 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, '$'), @@ -798,6 +829,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 3d58bf6589..857bef43b5 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 f941428427de1620750275ca4ff24c55ccb2f130 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:03:42 +0300 Subject: [PATCH 84/97] Add json support for JSON_EXISTS, JSON_VALUE, JSON_QUERY --- src/backend/executor/execExprInterp.c | 94 +- src/backend/parser/parse_expr.c | 13 - src/include/executor/execExpr.h | 2 +- src/include/utils/jsonpath.h | 6 + src/test/regress/expected/json_sqljson.out | 1033 +++++++++++++++++++- src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/json_sqljson.sql | 291 +++++- 7 files changed, 1386 insertions(+), 55 deletions(-) diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index c58b5d98cd..c787d35ab2 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4146,17 +4146,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); @@ -4183,17 +4187,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, @@ -4207,7 +4214,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, @@ -4241,7 +4248,7 @@ EvalJsonPathVar(void *cxt, bool *isnull) * corresponding SQL type and a pointer to the coercion state. */ Datum -ExecPrepareJsonItemCoercion(JsonbValue *item, +ExecPrepareJsonItemCoercion(JsonbValue *item, bool is_jsonb, JsonReturning *returning, struct JsonCoercionsState *coercions, struct JsonCoercionState **pcoercion) @@ -4250,8 +4257,14 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, Datum res; JsonbValue jbvbuf; - if (item->type == jbvBinary && JsonContainerIsScalar(item->val.binary.data)) - item = JsonbExtractScalar(item->val.binary.data, &jbvbuf); + if (item->type == jbvBinary) + { + if (JsonContainerIsScalar(item->val.binary.data)) + item = is_jsonb + ? JsonbExtractScalar(item->val.binary.data, &jbvbuf) + : JsonExtractScalar((JsonContainer *) item->val.binary.data, + &jbvbuf); + } /* get coercion state reference and datum of the corresponding SQL type */ switch (item->type) @@ -4308,7 +4321,18 @@ ExecPrepareJsonItemCoercion(JsonbValue *item, case jbvObject: case jbvBinary: coercion = &coercions->composite; - res = JsonbPGetDatum(JsonbValueToJsonb(item)); + if (is_jsonb) + { + Jsonb *jb = JsonbValueToJsonb(item); + + res = JsonbPGetDatum(jb); + } + else + { + Json *js = JsonbValueToJson(item); + + res = JsonPGetDatum(js); + } break; default: @@ -4323,7 +4347,8 @@ ExecPrepareJsonItemCoercion(JsonbValue *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; @@ -4339,7 +4364,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; } @@ -4348,15 +4374,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 = (isjsonb ? JsonbPathQuery : JsonPathQuery) + (item, path, jexpr->wrapper, &empty, op->d.jsonexpr.args); *resnull = !DatumGetPointer(res); break; case IS_JSON_VALUE: { - JsonbValue *jbv = JsonbPathValue(item, path, &empty, - op->d.jsonexpr.args); + JsonbValue *jbv = (isjsonb ? JsonbPathValue : JsonPathValue) + (item, path, &empty, op->d.jsonexpr.args); struct JsonCoercionState *jcstate; if (!jbv) @@ -4364,7 +4390,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, *resnull = false; - res = ExecPrepareJsonItemCoercion(jbv, + res = ExecPrepareJsonItemCoercion(jbv, isjsonb, &op->d.jsonexpr.jsexpr->returning, &op->d.jsonexpr.coercions, &jcstate); @@ -4377,8 +4403,11 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, jexpr->returning.typid == JSONBOID) { /* use coercion via I/O from json[b] to the output type */ - res = JsonbPGetDatum(JsonbValueToJsonb(jbv)); - res = ExecEvalJsonExprCoercion(op, econtext, res, resnull); + res = isjsonb + ? JsonbPGetDatum(JsonbValueToJsonb(jbv)) + : JsonPGetDatum(JsonbValueToJson(jbv)); + res = ExecEvalJsonExprCoercion(op, econtext, res, + resnull, isjsonb); } else if (jcstate->estate) { @@ -4392,7 +4421,8 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, break; case IS_JSON_EXISTS: - res = BoolGetDatum(JsonbPathExists(item, path, op->d.jsonexpr.args)); + res = BoolGetDatum((isjsonb ? JsonbPathExists : JsonPathExists) + (item, path, op->d.jsonexpr.args)); *resnull = false; break; @@ -4411,14 +4441,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); } 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); + res = ExecEvalJsonExprCoercion(op, econtext, res, resnull, isjsonb); return res; } @@ -4441,6 +4472,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; @@ -4448,7 +4483,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; @@ -4471,7 +4506,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 @@ -4498,7 +4533,7 @@ ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext) PG_TRY(); { res = ExecEvalJsonExpr(state, op, newecontext, jexpr, path, item, - op->resnull); + isjsonb, op->resnull); /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); @@ -4527,12 +4562,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 9c666368e8..ab24b3536e 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4685,13 +4685,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; @@ -4712,8 +4709,6 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) break; case IS_JSON_QUERY: - func_name = "JSON_QUERY"; - transformJsonFuncExprOutput(pstate, func, jsexpr); jsexpr->wrapper = func->wrapper; @@ -4722,8 +4717,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; @@ -4733,11 +4726,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/include/executor/execExpr.h b/src/include/executor/execExpr.h index 3fb1975679..bd3fbeefbb 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -788,7 +788,7 @@ extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext); -extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, +extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *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 c349e10802..4071093389 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -304,6 +304,12 @@ extern Datum JsonbPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, extern JsonbValue *JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars); +extern bool JsonPathExists(Datum json, JsonPath *path, List *vars); +extern JsonbValue *JsonPathValue(Datum json, JsonPath *jp, bool *empty, + List *vars); +extern Datum JsonPathQuery(Datum json, JsonPath *jp, JsonWrapper wrapper, + bool *empty, List *vars); + extern Datum EvalJsonPathVar(void *cxt, bool *isnull); #endif diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index bb62634314..5379289f2b 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1,15 +1,1028 @@ -- 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: SQL/JSON member not found +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); + json_value +------------ + \x313233 +(1 row) + +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 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 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: SQL/JSON scalar required +SELECT JSON_VALUE(json '{}', '$'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '{}', '$' ERROR ON ERROR); +ERROR: SQL/JSON scalar required +SELECT JSON_VALUE(json '1', '$.a'); + json_value +------------ + +(1 row) + +SELECT JSON_VALUE(json '1', 'strict $.a' ERROR ON ERROR); +ERROR: SQL/JSON member not found +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: more than one SQL/JSON 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 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: more than one SQL/JSON item +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 adsrc FROM pg_attrdef WHERE adrelid = 'test_json_constraints'::regclass; + adsrc +--------------------------------------------------------------------------------------------------------- + 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: bad jsonpath representation +DETAIL: syntax error, unexpected IDENT_P at or near " " diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 498542ed19..f3f69ac232 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,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 json_jsonpath jsonb_jsonpath jsonb_sqljson sqljson +test: json jsonb json_encoding jsonpath json_jsonpath jsonb_jsonpath json_sqljson jsonb_sqljson sqljson # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/json_sqljson.sql b/src/test/regress/sql/json_sqljson.sql index 4f30fa46b9..084c7b1961 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); + +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 adsrc 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 3e7aef8c8dadd7cc7a0f288a0c4f0e132066dc72 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 02:45:51 +0300 Subject: [PATCH 85/97] 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 5379289f2b..f7d1568145 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -559,6 +559,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, '$'), @@ -921,6 +952,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 084c7b1961..6146c4500e 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 1b6366f5efb2935fae3b0aa5d57b49e8607fa2d5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 25 Jul 2017 00:46:14 +0300 Subject: [PATCH 86/97] 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 | 13 + 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 | 289 +++++- src/backend/parser/parse_clause.c | 662 ++++++++++++ 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 | 487 +++++++++ src/backend/utils/adt/ruleutils.c | 284 +++++- src/include/executor/execExpr.h | 3 + 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 | 6 +- 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, 3706 insertions(+), 25 deletions(-) diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 1f0e58178e..b5743489a8 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -2924,9 +2924,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 73d94b7235..59978220ec 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2925,7 +2925,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 5b84312b85..2cee26fc99 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -2191,6 +2191,7 @@ ExecInitExprRec(Expr *node, ExprState *state, var->var.cb_arg = var; 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 c787d35ab2..7dba1d97a3 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -4170,6 +4170,7 @@ ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior, case JSON_BEHAVIOR_NULL: case JSON_BEHAVIOR_UNKNOWN: + case JSON_BEHAVIOR_EMPTY: *is_null = true; return (Datum) 0; @@ -4235,8 +4236,14 @@ EvalJsonPathVar(void *cxt, bool *isnull) if (!ecxt->evaluated) { + MemoryContext oldcxt = ecxt->mcxt ? + MemoryContextSwitchTo(ecxt->mcxt) : NULL; + ecxt->value = ExecEvalExpr(ecxt->estate, ecxt->econtext, &ecxt->isnull); ecxt->evaluated = true; + + if (oldcxt) + MemoryContextSwitchTo(oldcxt); } *isnull = ecxt->isnull; @@ -4426,6 +4433,11 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, *resnull = false; break; + case IS_JSON_TABLE: + res = item; + *resnull = false; + break; + default: elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op); @@ -4446,6 +4458,7 @@ ExecEvalJsonExpr(ExprState *state, ExprEvalStep *op, ExprContext *econtext, } if (jexpr->op != IS_JSON_EXISTS && + jexpr->op != IS_JSON_TABLE && (!empty ? jexpr->op != IS_JSON_VALUE : /* result is already coerced in DEFAULT behavior case */ jexpr->on_empty.btype != JSON_BEHAVIOR_DEFAULT)) diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index fed6f2b3a5..dfae539df6 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" @@ -161,8 +162,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->perValueCxt = AllocSetContextCreate(CurrentMemoryContext, @@ -365,14 +367,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 8f51a0e661..fe3977a85c 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1288,6 +1288,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); @@ -1298,7 +1299,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); @@ -2505,6 +2508,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; +} + /* **************************************************************** * relation.h copy functions * @@ -5446,6 +5542,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 cd3ee22840..d22d9348d4 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -119,6 +119,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); @@ -129,13 +130,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) { @@ -3286,6 +3312,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 ebc41ea633..334cabed19 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -660,6 +660,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 f699977340..c201ebfd8d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2291,6 +2291,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: @@ -3172,6 +3174,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; @@ -3993,6 +3996,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 9529717bd5..79c0bc0d49 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1108,6 +1108,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); @@ -1118,7 +1119,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); } @@ -1859,6 +1862,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 relation.h. @@ -4432,6 +4458,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 6954854dd9..00397c0c99 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -479,6 +479,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); @@ -489,7 +490,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); @@ -1403,6 +1406,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 */ @@ -2880,6 +2910,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 13ab62798d..8b70480d11 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -613,9 +613,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 @@ -624,9 +644,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 @@ -640,6 +664,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 @@ -647,6 +672,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 @@ -718,7 +745,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 @@ -728,7 +755,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 @@ -736,7 +763,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 @@ -852,6 +879,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 @@ -11989,6 +12020,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; + } ; @@ -12492,6 +12536,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: @@ -14907,6 +14953,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); } ; @@ -15018,6 +15068,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 json_path_specification { $$ = $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 json_path_specification + 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 @@ -15787,6 +16066,7 @@ unreserved_keyword: | MOVE | NAME_P | NAMES + | NESTED | NEW | NEXT | NO @@ -15815,6 +16095,8 @@ unreserved_keyword: | PARTITION | PASSING | PASSWORD + | PATH + | PLAN | PLANS | POLICY | PRECEDING @@ -15971,6 +16253,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 e1478805c2..fb66c65c65 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -49,10 +49,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) @@ -103,6 +113,10 @@ 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); /* @@ -760,6 +774,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, @@ -1079,6 +1095,627 @@ 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 (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; + } + + 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 @@ -1211,6 +1848,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 ab24b3536e..cb6407a9c2 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4417,7 +4417,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"), @@ -4463,14 +4463,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; } @@ -4724,6 +4723,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 bf5df26009..47b9831bb1 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1662,7 +1662,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 7a4351541d..b40de130ee 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1944,6 +1944,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 a252383857..bf3275d78a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -15,12 +15,16 @@ #include "miscadmin.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" +#include "executor/execExpr.h" #include "lib/stringinfo.h" +#include "nodes/nodeFuncs.h" #include "regex/regex.h" #include "utils/builtins.h" #include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/varlena.h" #ifdef JSONPATH_JSON_C @@ -55,6 +59,58 @@ typedef struct JsonValueListIterator #define JsonValueListIteratorEnd ((ListCell *) -1) +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; + static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); @@ -64,6 +120,11 @@ static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt static inline JsonbValue *wrapItemsInArray(const JsonValueList *items); +static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, + Node *plan, JsonTableScanState *parent); + +static bool JsonTableNextRow(JsonTableScanState *scan); + static inline void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv) @@ -2813,3 +2874,429 @@ JsonbPathValue(Datum jb, JsonPath *jp, bool *empty, List *vars) return res; } + +/* + * 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->var.varName = cstring_to_text(name->val.str); + var->var.typid = exprType((Node *) expr); + var->var.typmod = exprTypmod((Node *) expr); + var->var.cb = EvalJsonPathVar; + var->var.cb_arg = var; + 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) +{ + memset(&scan->iter, 0, sizeof(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; + + JsonValueListClear(&scan->found); + + MemoryContextResetOnly(scan->mcxt); + + oldcxt = MemoryContextSwitchTo(scan->mcxt); + + res = executeJsonPath(scan->path, scan->args, DatumGetJsonbP(item), + &scan->found); + + MemoryContextSwitchTo(oldcxt); + + if (jperIsError(res)) + { + if (scan->errorOnError) + throwJsonPathError(res); /* does not return */ + else + 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 */ + JsonbValue *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(JsonbValueToJsonb(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 d6776a04c9..9063a5b662 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -469,6 +469,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 ") @@ -8940,6 +8942,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); @@ -9974,16 +9979,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) @@ -10087,6 +10090,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 bd3fbeefbb..561e9a9641 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -793,6 +793,9 @@ extern Datum ExecPrepareJsonItemCoercion(struct JsonbValue *item, bool is_jsonb, struct JsonCoercionsState *coercions, struct JsonCoercionState **pjcstate); extern bool ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr); +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 f7aec03b9f..78488bf0a8 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -90,6 +90,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 4527b5e759..25f1b46f1a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -198,6 +198,8 @@ typedef enum NodeTag T_JsonExpr, T_JsonCoercion, T_JsonItemCoercions, + T_JsonTableParentNode, + T_JsonTableSiblingNode, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -486,6 +488,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 857f0b5d26..1622f416ae 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1441,6 +1441,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 @@ -1511,6 +1523,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 4bfa016db9..19745dcf9a 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -73,12 +73,19 @@ 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. */ typedef struct TableFunc { NodeTag type; + TableFuncType functype; /* XMLTABLE or JSON_TABLE */ List *ns_uris; /* list of namespace uri */ List *ns_names; /* list of namespace names */ Node *docexpr; /* input document expression */ @@ -89,7 +96,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; @@ -1182,7 +1191,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; /* @@ -1335,6 +1345,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 7732c1bd49..95eae048bb 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -233,6 +233,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) @@ -273,6 +274,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) @@ -316,7 +318,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 4071093389..c46901021c 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -15,9 +15,10 @@ #define JSONPATH_H #include "fmgr.h" -#include "utils/jsonb.h" +#include "executor/tablefunc.h" #include "nodes/pg_list.h" #include "nodes/primnodes.h" +#include "utils/jsonb.h" typedef struct { @@ -282,6 +283,7 @@ typedef struct JsonPathVariableEvalContext JsonPathVariable var; struct ExprContext *econtext; struct ExprState *estate; + MemoryContext mcxt; /* memory context for cached value */ Datum value; bool isnull; bool evaluated; @@ -312,4 +314,6 @@ extern Datum JsonPathQuery(Datum json, JsonPath *jp, JsonWrapper wrapper, extern Datum EvalJsonPathVar(void *cxt, bool *isnull); +extern const TableFuncRoutine JsonbTableRoutine; + #endif diff --git a/src/test/regress/expected/json_sqljson.out b/src/test/regress/expected/json_sqljson.out index f7d1568145..fc26f2719a 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1041,6 +1041,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 c516870004..6044f17aa6 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -918,6 +918,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 | t | 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 | t | 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 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 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: SQL/JSON member not found +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; + bar | foo | baz +-----+-----+----- +(0 rows) + +-- 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 'x' passed variable -- 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 6146c4500e..896941c9ae 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 857bef43b5..9a8b6fc820 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 11bea7b433abd063ef27fbef63485422f85502f9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 7 Dec 2017 19:19:51 +0300 Subject: [PATCH 87/97] Allow variable jsonpath specifications in JSON_TABLE --- src/backend/parser/gram.y | 6 ++-- 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 +- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8b70480d11..259cd5b672 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -854,7 +854,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 '+' '-' @@ -15147,7 +15147,7 @@ json_table_error_clause_opt: ; json_table_column_path_specification_clause_opt: - PATH json_path_specification { $$ = $2; } + PATH Sconst { $$ = $2; } | /* EMPTY */ %prec json_table_column { $$ = NULL; } ; @@ -15179,7 +15179,7 @@ json_table_formatted_column_definition: ; json_table_nested_columns: - NESTED path_opt json_path_specification + NESTED path_opt Sconst json_as_path_name_clause_opt json_table_columns_clause { diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index fb66c65c65..a6ad5f020c 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -1065,6 +1065,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 * @@ -1108,6 +1120,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; @@ -1128,7 +1141,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"' */ @@ -1139,9 +1152,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; @@ -1642,6 +1657,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; @@ -1671,7 +1687,7 @@ transformJsonTable(ParseState *pstate, JsonTable *jt) } jscommon = copyObject(jt->common); - jscommon->pathspec = pstrdup("$"); + jscommon->pathspec = makeStringConst(pstrdup("$"), -1); jfe->op = IS_JSON_TABLE; jfe->common = jscommon; @@ -1695,10 +1711,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 9063a5b662..3e4b35f8ff 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -10233,7 +10233,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 6044f17aa6..e3f4e93c7e 100644 --- a/src/test/regress/expected/jsonb_sqljson.out +++ b/src/test/regress/expected/jsonb_sqljson.out @@ -1899,6 +1899,11 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error'); ERROR: bad jsonpath representation DETAIL: syntax error, unexpected IDENT_P at or near " " +-- 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 9a8b6fc820..7dbdfc2e1f 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 7c1dd5f03a28f673055d833ab071ddeabb97fb62 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:05:11 +0300 Subject: [PATCH 88/97] Add json support for JSON_TABLE --- src/backend/executor/nodeTableFuncscan.c | 5 +- src/backend/parser/parse_expr.c | 6 - src/include/utils/jsonpath.h | 1 + src/test/regress/expected/json_sqljson.out | 981 ++++++++++++++++++++- src/test/regress/sql/json_sqljson.sql | 587 +++++++++++- 5 files changed, 1568 insertions(+), 12 deletions(-) diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index dfae539df6..15d2d32e79 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" @@ -164,7 +166,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->perValueCxt = AllocSetContextCreate(CurrentMemoryContext, diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index cb6407a9c2..14fa2e61cf 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4731,12 +4731,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/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index c46901021c..fa4346e0e2 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -314,6 +314,7 @@ extern Datum JsonPathQuery(Datum json, JsonPath *jp, JsonWrapper wrapper, extern Datum EvalJsonPathVar(void *cxt, bool *isnull); +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 fc26f2719a..97c53dd4ad 100644 --- a/src/test/regress/expected/json_sqljson.out +++ b/src/test/regress/expected/json_sqljson.out @@ -1042,10 +1042,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 | t | 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 | t | 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 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: SQL/JSON member not found +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; + bar | foo | baz +-----+-----+----- +(0 rows) + +-- 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 'x' passed variable -- Extension: non-constant JSON path SELECT JSON_EXISTS(json '{"a": 123}', '$' || '.' || 'a'); json_exists @@ -1081,3 +2049,8 @@ SELECT JSON_QUERY(json '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER); SELECT JSON_QUERY(json '{"a": 123}', 'error' || ' ' || 'error'); ERROR: bad jsonpath representation DETAIL: syntax error, unexpected IDENT_P at or near " " +-- 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 896941c9ae..90c52c3c8b 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 7c6a98975373cb6decf64f9c3bc4b54400e9e2a0 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 23 Mar 2017 01:33:10 +0300 Subject: [PATCH 89/97] Add jsonpath datetime() extension: UNIX epoch time to timestamptz --- src/backend/utils/adt/jsonpath_exec.c | 146 +++++++++++-------- src/test/regress/expected/jsonb_jsonpath.out | 21 ++- src/test/regress/sql/jsonb_jsonpath.sql | 6 +- 3 files changed, 111 insertions(+), 62 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index bf3275d78a..f22f04d5d7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2042,7 +2042,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, { JsonbValue jbvbuf; Datum value; - text *datetime; Oid typid; int32 typmod = -1; int tz; @@ -2053,84 +2052,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION); - if (jb->type != jbvString) - break; + if (jb->type == jbvNumeric && !jsp->content.args.left) + { + /* Standard extension: unix epoch to timestamptz */ + MemoryContext mcxt = CurrentMemoryContext; - datetime = cstring_to_text_with_len(jb->val.string.val, - jb->val.string.len); + PG_TRY(); + { + Datum unix_epoch = + DirectFunctionCall1(numeric_float8, + NumericGetDatum(jb->val.numeric)); + + value = DirectFunctionCall1(float8_timestamptz, + unix_epoch); + typid = TIMESTAMPTZOID; + tz = 0; + res = jperOk; + } + PG_CATCH(); + { + if (ERRCODE_TO_CATEGORY(geterrcode()) != + ERRCODE_DATA_EXCEPTION) + PG_RE_THROW(); - if (jsp->content.args.left) + FlushErrorState(); + MemoryContextSwitchTo(mcxt); + } + PG_END_TRY(); + } + else if (jb->type == jbvString) { - char *template_str; - int template_len; - char *tzname = NULL; + text *datetime = + cstring_to_text_with_len(jb->val.string.val, + jb->val.string.len); - jspGetLeftArg(jsp, &elem); + if (jsp->content.args.left) + { + char *template_str; + int template_len; + char *tzname = NULL; - if (elem.type != jpiString) - elog(ERROR, "invalid jsonpath item type for .datetime() argument"); + jspGetLeftArg(jsp, &elem); - template_str = jspGetString(&elem, &template_len); + if (elem.type != jpiString) + elog(ERROR, "invalid jsonpath item type for .datetime() argument"); - if (jsp->content.args.right) - { - JsonValueList tzlist = { 0 }; - JsonPathExecResult tzres; - JsonbValue *tzjbv; + template_str = jspGetString(&elem, &template_len); - jspGetRightArg(jsp, &elem); - tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb, - &tzlist); + if (jsp->content.args.right) + { + JsonValueList tzlist = { 0 }; + JsonPathExecResult tzres; + JsonbValue *tzjbv; - if (jperIsError(tzres)) - return tzres; + jspGetRightArg(jsp, &elem); + tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb, + &tzlist); - if (JsonValueListLength(&tzlist) != 1) - break; + if (jperIsError(tzres)) + return tzres; - tzjbv = JsonValueListHead(&tzlist); + if (JsonValueListLength(&tzlist) != 1) + break; - if (tzjbv->type != jbvString) - break; + tzjbv = JsonValueListHead(&tzlist); - tzname = pnstrdup(tzjbv->val.string.val, - tzjbv->val.string.len); - } + if (tzjbv->type != jbvString) + break; - if (tryToParseDatetime(template_str, template_len, datetime, - tzname, false, - &value, &typid, &typmod, &tz)) - res = jperOk; + tzname = pnstrdup(tzjbv->val.string.val, + tzjbv->val.string.len); + } - if (tzname) - pfree(tzname); - } - else - { - const char *templates[] = { - "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" - }; - int i; - - for (i = 0; i < sizeof(templates) / sizeof(*templates); i++) + if (tryToParseDatetime(template_str, template_len, + datetime, tzname, false, + &value, &typid, &typmod, &tz)) + res = jperOk; + + if (tzname) + pfree(tzname); + } + else { - if (tryToParseDatetime(templates[i], -1, datetime, - NULL, true, &value, &typid, - &typmod, &tz)) + const char *templates[] = { + "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" + }; + int i; + + for (i = 0; i < sizeof(templates) / sizeof(*templates); i++) { - res = jperOk; - break; + if (tryToParseDatetime(templates[i], -1, datetime, + NULL, true, &value, &typid, + &typmod, &tz)) + { + res = jperOk; + break; + } } } - } - pfree(datetime); + pfree(datetime); + } if (jperIsError(res)) break; diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 22f7255b5c..3d17ea173f 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1179,8 +1179,6 @@ select jsonb 'null' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function select jsonb 'true' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function -select jsonb '1' @* '$.datetime()'; -ERROR: Invalid argument for SQL/JSON datetime function select jsonb '[]' @* '$.datetime()'; ?column? ---------- @@ -1192,6 +1190,25 @@ select jsonb '{}' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function select jsonb '""' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function +-- Standard extension: UNIX epoch to timestamptz +select jsonb '0' @* '$.datetime()'; + ?column? +----------------------------- + "1970-01-01T00:00:00+00:00" +(1 row) + +select jsonb '0' @* '$.datetime().type()'; + ?column? +---------------------------- + "timestamp with time zone" +(1 row) + +select jsonb '1490216035.5' @* '$.datetime()'; + ?column? +------------------------------- + "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 369463d844..655dcf0d9a 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -240,12 +240,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? select jsonb 'null' @* '$.datetime()'; select jsonb 'true' @* '$.datetime()'; -select jsonb '1' @* '$.datetime()'; select jsonb '[]' @* '$.datetime()'; select jsonb '[]' @* 'strict $.datetime()'; select jsonb '{}' @* '$.datetime()'; select jsonb '""' @* '$.datetime()'; +-- Standard extension: UNIX epoch to timestamptz +select jsonb '0' @* '$.datetime()'; +select jsonb '0' @* '$.datetime().type()'; +select jsonb '1490216035.5' @* '$.datetime()'; + select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; From c27849ab5bac4fa99305659abdb5825b25f7c727 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 5 Apr 2017 22:53:12 +0300 Subject: [PATCH 90/97] Add jsonpath sequences --- src/backend/utils/adt/jsonpath.c | 66 ++++++++++++++++++-- src/backend/utils/adt/jsonpath_exec.c | 45 +++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 31 ++++++++- src/include/utils/jsonpath.h | 11 ++++ src/test/regress/expected/jsonb_jsonpath.out | 54 ++++++++++++++++ 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, 252 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d07423b8ea..7fe737ff21 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -215,6 +215,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 pos = + flattenJsonPathParseItem(buf, lfirst(lc), + allowCurrent, insideArraySubscript); + + *(int32 *) &buf->data[offset] = pos; + offset += sizeof(int32); + } + } + break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -300,6 +323,8 @@ operationPriority(JsonPathItemType op) { switch (op) { + case jpiSequence: + return -1; case jpiOr: return 0; case jpiAnd: @@ -489,12 +514,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket 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, ']'); @@ -558,6 +583,25 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket 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, "Unknown jsonpath item type: %d", v->type); } @@ -580,7 +624,7 @@ jsonpath_out(PG_FUNCTION_ARGS) appendBinaryStringInfo(&buf, "strict ", 7); jspInit(&v, in); - printJsonPathItem(&buf, &v, false, true); + printJsonPathItem(&buf, &v, false, v.type != jpiSequence); PG_RETURN_CSTRING(buf.data); } @@ -702,6 +746,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, "Unknown jsonpath item type: %d", v->type); } @@ -768,7 +817,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) @@ -872,3 +922,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 f22f04d5d7..f738bb2920 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2261,6 +2261,51 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } break; + 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++) + { + JsonbValue *v; + + if (hasNext) + memset(&list, 0, sizeof(list)); + + jspGetSequenceElement(jsp, i, &elem); + res = recursiveExecute(cxt, &elem, jb, plist); + + if (jperIsError(res)) + break; + + if (!hasNext) + { + if (!found && res == jperOk) + break; + continue; + } + + memset(&it, 0, sizeof(it)); + + while ((v = JsonValueListNext(&list, &it))) + { + res = recursiveExecute(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 6c409a47b8..8aa374bd44 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -252,6 +252,16 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) return v; } +static JsonPathParseItem * +makeItemSequence(List *elems) +{ + JsonPathParseItem *v = makeItemType(jpiSequence); + + v->value.sequence.elems = elems; + + return v; +} + %} /* BISON Declarations */ @@ -285,9 +295,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial datetime_template opt_datetime_template - expr_or_predicate + expr_or_predicate expr_or_seq expr_seq -%type accessor_expr +%type accessor_expr expr_list %type index_list @@ -311,7 +321,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %% result: - mode expr_or_predicate { + mode expr_or_seq { *result = palloc(sizeof(JsonPathParseResult)); (*result)->expr = $2; (*result)->lax = $1; @@ -324,6 +334,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; } @@ -378,6 +402,7 @@ path_primary: | '$' { $$ = makeItemType(jpiRoot); } | '@' { $$ = makeItemType(jpiCurrent); } | LAST_P { $$ = makeItemType(jpiLast); } + | '(' expr_seq ')' { $$ = $2; } ; accessor_expr: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index fa4346e0e2..8578962f74 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -86,6 +86,7 @@ typedef enum JsonPathItemType { jpiLast, jpiStartsWith, jpiLikeRegex, + jpiSequence, } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ @@ -139,6 +140,11 @@ typedef struct JsonPathItem { uint32 last; } anybounds; + struct { + int32 nelems; + int32 *elems; + } sequence; + struct { char *data; /* for bool, numeric and string/key */ int32 datalen; /* filled only for string/key */ @@ -166,6 +172,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); /* * Parsing @@ -211,6 +218,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 3d17ea173f..9efeba05f5 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1726,3 +1726,57 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; f (1 row) +-- extension: path sequences +select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30'; + ?column? +---------- + 10 + 20 + 1 + 2 + 3 + 4 + 5 + 30 +(8 rows) + +select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30'; + ?column? +---------- + 10 + 20 + 30 +(3 rows) + +select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30'; +ERROR: SQL/JSON member not found +select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)'; + ?column? +---------- + -10 + -20 + -2 + -3 + -4 + -30 +(6 rows) + +select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()'; + ?column? +---------- + 10 + 20.5 + 2 + 3 + 4 + 30 +(6 rows) + +select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; + ?column? +---------- + 4 +(1 row) + +select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]'; +ERROR: Invalid SQL/JSON subscript diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 8cd0534195..767cf24799 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -510,6 +510,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 655dcf0d9a..801b8b1ea6 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -387,3 +387,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; + +-- extension: path sequences +select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30'; +select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30'; +select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30'; +select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)'; +select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()'; +select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; +select jsonb '[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 7b546873dd..aa64700e87 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -96,6 +96,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 8e6fc9d2f0cf475c1b48bdb44c7d61a4693fafac Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Apr 2017 22:36:05 +0300 Subject: [PATCH 91/97] Add jsonpath array constructors --- src/backend/utils/adt/jsonpath.c | 23 ++++++-- src/backend/utils/adt/jsonpath_exec.c | 18 +++++++ src/backend/utils/adt/jsonpath_gram.y | 2 + src/include/utils/jsonpath.h | 1 + src/test/regress/expected/jsonb_jsonpath.out | 57 ++++++++++++++++++++ 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, 123 insertions(+), 4 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 7fe737ff21..48d1c61562 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -132,12 +132,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiPlus: case jpiMinus: case jpiExists: + case jpiArray: { - int32 arg; + int32 arg = item->value.arg ? buf->len : 0; - arg = buf->len; appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); + if (!item->value.arg) + break; + chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || allowCurrent, @@ -602,6 +605,15 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket 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, "Unknown jsonpath item type: %d", v->type); } @@ -735,6 +747,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: @@ -765,7 +778,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); @@ -818,7 +832,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) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index f738bb2920..bb1e97f402 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2306,6 +2306,24 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, break; } + case jpiArray: + { + JsonValueList list = { 0 }; + + if (jsp->content.arg) + { + jspGetArg(jsp, &elem); + res = recursiveExecute(cxt, &elem, jb, &list); + + if (jperIsError(res)) + break; + } + + res = recursiveExecuteNext(cxt, jsp, NULL, + wrapItemsInArray(&list), + found, false); + } + 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 8aa374bd44..db7397f4b4 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -403,6 +403,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 8578962f74..bff225fa07 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -87,6 +87,7 @@ typedef enum JsonPathItemType { jpiStartsWith, jpiLikeRegex, jpiSequence, + jpiArray, } 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 9efeba05f5..628816aeaf 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1684,6 +1684,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; ---------- (0 rows) +SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]'; + ?column? +---------- + [1, 2] +(1 row) + SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a'; ?column? ---------- @@ -1702,6 +1708,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; (1 row) +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]'; + ?column? +---------- + [1, 2] +(1 row) + SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; ?column? ---------- @@ -1780,3 +1792,48 @@ select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]'; ERROR: Invalid SQL/JSON subscript +-- extension: array constructors +select jsonb '[1, 2, 3]' @* '[]'; + ?column? +---------- + [] +(1 row) + +select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]'; + ?column? +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]'; + ?column? +---------- + 1 + 2 + 1 + 2 + 3 + 4 + 5 +(7 rows) + +select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]'; + ?column? +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'; + ?column? +------------------------------- + [[1, 2], [1, 2, 3, 4], 5, []] +(1 row) + +select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]'; +ERROR: SQL/JSON member not found +select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]'; + ?column? +-------------- + [4, 5, 6, 7] +(1 row) + diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 767cf24799..de6e5909d4 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -546,6 +546,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 801b8b1ea6..1c4a68f5f2 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -377,10 +377,12 @@ set time zone default; SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; +SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)'; SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)'; @@ -396,3 +398,12 @@ select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)'; select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()'; select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]'; + +-- extension: array constructors +select jsonb '[1, 2, 3]' @* '[]'; +select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]'; +select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]'; +select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]'; +select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'; +select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]'; +select jsonb '[[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 aa64700e87..c570702030 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -103,6 +103,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 d95b4a10e00669b3b6b91f04b82d65e16081edd6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Apr 2017 23:34:14 +0300 Subject: [PATCH 92/97] Add jsonpath object constructors --- src/backend/utils/adt/jsonpath.c | 68 +++++++++++++++++++- src/backend/utils/adt/jsonpath_exec.c | 64 ++++++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 26 +++++++- src/include/utils/jsonpath.h | 16 +++++ 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, 235 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 48d1c61562..e92600d5d2 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -241,6 +241,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, + allowCurrent, + insideArraySubscript); + int32 valpos = + flattenJsonPathParseItem(buf, field->value.args.right, + allowCurrent, + insideArraySubscript); + int32 *ppos = (int32 *) &buf->data[offset]; + + ppos[0] = keypos; + ppos[1] = valpos; + + offset += 2 * sizeof(int32); + } + } + break; default: elog(ERROR, "Unknown jsonpath item type: %d", item->type); } @@ -614,6 +646,26 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket } 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, "Unknown jsonpath item type: %d", v->type); } @@ -764,6 +816,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, "Unknown jsonpath item type: %d", v->type); } @@ -833,7 +890,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) @@ -945,3 +1003,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 bb1e97f402..a1ea4d2ae4 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2324,6 +2324,70 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, found, false); } break; + case jpiObject: + { + JsonbParseState *ps = NULL; + JsonbValue *obj; + int i; + + pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < jsp->content.object.nfields; i++) + { + JsonbValue *jbv; + JsonbValue jbvtmp; + JsonPathItem key; + JsonPathItem val; + JsonValueList key_list = { 0 }; + JsonValueList val_list = { 0 }; + + jspGetObjectField(jsp, i, &key, &val); + + recursiveExecute(cxt, &key, jb, &key_list); + + if (JsonValueListLength(&key_list) != 1) + { + res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); + break; + } + + jbv = JsonValueListHead(&key_list); + + if (JsonbType(jbv) == jbvScalar) + jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp); + + if (jbv->type != jbvString) + { + res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */ + break; + } + + pushJsonbValue(&ps, WJB_KEY, jbv); + + recursiveExecute(cxt, &val, jb, &val_list); + + if (JsonValueListLength(&val_list) != 1) + { + res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED); + break; + } + + jbv = JsonValueListHead(&val_list); + + if (jbv->type == jbvObject || jbv->type == jbvArray) + jbv = JsonbWrapInBinary(jbv, &jbvtmp); + + pushJsonbValue(&ps, WJB_VALUE, jbv); + } + + if (jperIsError(res)) + break; + + obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); + + res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false); + } + 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 db7397f4b4..ada03d08b0 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -262,6 +262,16 @@ makeItemSequence(List *elems) return v; } +static JsonPathParseItem * +makeItemObject(List *fields) +{ + JsonPathParseItem *v = makeItemType(jpiObject); + + v->value.object.fields = fields; + + return v; +} + %} /* BISON Declarations */ @@ -295,9 +305,9 @@ makeItemSequence(List *elems) %type scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial datetime_template opt_datetime_template - expr_or_predicate expr_or_seq expr_seq + expr_or_predicate expr_or_seq expr_seq object_field -%type accessor_expr expr_list +%type accessor_expr expr_list object_field_list %type index_list @@ -405,6 +415,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: diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index bff225fa07..603a2e5e97 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -88,6 +88,8 @@ typedef enum JsonPathItemType { jpiLikeRegex, jpiSequence, jpiArray, + jpiObject, + jpiObjectField, } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ @@ -146,6 +148,14 @@ 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 */ int32 datalen; /* filled only for string/key */ @@ -174,6 +184,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); /* * Parsing @@ -223,6 +235,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 628816aeaf..6b320b56f2 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1837,3 +1837,37 @@ select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]'; [4, 5, 6, 7] (1 row) +-- extension: object constructors +select jsonb '[1, 2, 3]' @* '{}'; + ?column? +---------- + {} +(1 row) + +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}'; + ?column? +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*'; + ?column? +----------------- + 5 + [1, 2, 3, 4, 5] +(2 rows) + +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'; + ?column? +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}'; +ERROR: Singleton SQL/JSON item required +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'; + ?column? +--------------------------------------------------------- + {"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 de6e5909d4..1cf0dc3ac1 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -558,6 +558,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 1c4a68f5f2..78677046ff 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -407,3 +407,11 @@ select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]'; select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'; select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]'; select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]'; + +-- extension: object constructors +select jsonb '[1, 2, 3]' @* '{}'; +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}'; +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*'; +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'; +select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}'; +select jsonb '[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 c570702030..cb1688b657 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -106,6 +106,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 e6aea20a2958d7a6cc1e1e3922c02bf60cb3ea1e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 12 May 2017 00:07:24 +0300 Subject: [PATCH 93/97] Add jsonpath object subscripting --- src/backend/utils/adt/jsonpath_exec.c | 125 ++++++++++++++++++- src/test/regress/expected/json_jsonpath.out | 6 +- src/test/regress/expected/jsonb_jsonpath.out | 96 ++++++++++++++ src/test/regress/sql/jsonb_jsonpath.sql | 24 ++++ 4 files changed, 245 insertions(+), 6 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a1ea4d2ae4..bbfca30946 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -118,6 +118,8 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); +static inline JsonbValue *wrapItem(JsonbValue *jbv); + static inline JsonbValue *wrapItemsInArray(const JsonValueList *items); static JsonTableJoinState *JsonTableInitPlanState(JsonTableContext *cxt, @@ -1765,9 +1767,127 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, cxt->innermostArraySize = innermostArraySize; } - else if (!jspIgnoreStructuralErrors(cxt)) + else if (JsonbType(jb) == jbvObject) { - res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); + int innermostArraySize = cxt->innermostArraySize; + int i; + JsonbValue bin; + JsonbValue *wrapped = NULL; + + if (jb->type != jbvBinary) + jb = JsonbWrapInBinary(jb, &bin); + + cxt->innermostArraySize = 1; + + for (i = 0; i < jsp->content.array.nelems; i++) + { + JsonPathItem from; + JsonPathItem to; + JsonbValue *key; + JsonbValue tmp; + JsonValueList keys = { 0 }; + bool range = jspGetArraySubscript(jsp, &from, &to, i); + + if (range) + { + int index_from; + int index_to; + + if (!jspAutoWrap(cxt)) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + + if (!wrapped) + wrapped = wrapItem(jb); + + res = getArrayIndex(cxt, &from, wrapped, &index_from); + if (jperIsError(res)) + return res; + + res = getArrayIndex(cxt, &to, wrapped, &index_to); + if (jperIsError(res)) + return res; + + res = jperNotFound; + + if (index_from <= 0 && index_to >= 0) + { + res = recursiveExecuteNext(cxt, jsp, NULL, jb, + found, true); + if (jperIsError(res)) + return res; + + } + + if (res == jperOk && !found) + break; + + continue; + } + + res = recursiveExecute(cxt, &from, jb, &keys); + + if (jperIsError(res)) + return res; + + if (JsonValueListLength(&keys) != 1) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + + key = JsonValueListHead(&keys); + + if (JsonbType(key) == jbvScalar) + key = JsonbExtractScalar(key->val.binary.data, &tmp); + + res = jperNotFound; + + if (key->type == jbvNumeric && jspAutoWrap(cxt)) + { + int index = DatumGetInt32( + DirectFunctionCall1(numeric_int4, + DirectFunctionCall2(numeric_trunc, + NumericGetDatum(key->val.numeric), + Int32GetDatum(0)))); + + if (!index) + { + res = recursiveExecuteNext(cxt, jsp, NULL, jb, + found, true); + if (jperIsError(res)) + return res; + } + else if (!jspIgnoreStructuralErrors(cxt)) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + } + else if (key->type == jbvString) + { + key = findJsonbValueFromContainer(jb->val.binary.data, + JB_FOBJECT, key); + + if (key) + { + res = recursiveExecuteNext(cxt, jsp, NULL, key, + found, false); + if (jperIsError(res)) + return res; + } + else if (!jspIgnoreStructuralErrors(cxt)) + return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); + } + else if (!jspIgnoreStructuralErrors(cxt)) + return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT); + + if (res == jperOk && !found) + break; + } + + cxt->innermostArraySize = innermostArraySize; + } + else + { + if (jspAutoWrap(cxt)) + res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb), + found); + else if (!jspIgnoreStructuralErrors(cxt)) + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); } break; @@ -2523,7 +2643,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return recursiveExecuteUnwrap(cxt, jsp, jb, found); case jpiAnyArray: - case jpiIndexArray: jb = wrapItem(jb); break; diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index 09d7df0c11..eb002ddcdd 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]'; select json '[1]' @* 'strict $[1.2]'; ERROR: Invalid SQL/JSON subscript select json '{}' @* 'strict $[0.3]'; -ERROR: SQL/JSON array not found +ERROR: Invalid SQL/JSON subscript select json '{}' @? 'lax $[0.3]'; ?column? ---------- @@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]'; (1 row) select json '{}' @* 'strict $[1.2]'; -ERROR: SQL/JSON array not found +ERROR: Invalid SQL/JSON subscript select json '{}' @? 'lax $[1.2]'; ?column? ---------- @@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]'; (1 row) select json '{}' @* 'strict $[-2 to 3]'; -ERROR: SQL/JSON array not found +ERROR: Invalid SQL/JSON subscript select json '{}' @? 'lax $[-2 to 3]'; ?column? ---------- diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 6b320b56f2..a7c19fb5b2 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]'; (1 row) +select jsonb '[1]' @* 'strict $[1.2]'; +ERROR: Invalid SQL/JSON subscript +select jsonb '{}' @* 'strict $[0.3]'; +ERROR: Invalid SQL/JSON subscript +select jsonb '{}' @? 'lax $[0.3]'; + ?column? +---------- + t +(1 row) + +select jsonb '{}' @* 'strict $[1.2]'; +ERROR: Invalid SQL/JSON subscript +select jsonb '{}' @? 'lax $[1.2]'; + ?column? +---------- + f +(1 row) + +select jsonb '{}' @* 'strict $[-2 to 3]'; +ERROR: Invalid SQL/JSON subscript +select jsonb '{}' @? 'lax $[-2 to 3]'; + ?column? +---------- + t +(1 row) + select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])'; ?column? ---------- @@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]'; 1 (1 row) +select jsonb '{}' @* 'lax $[0]'; + ?column? +---------- + {} +(1 row) + select jsonb '[1]' @* 'lax $[0]'; ?column? ---------- @@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]'; 1 (1 row) +select jsonb '{}' @* 'lax $[last]'; + ?column? +---------- + {} +(1 row) + select jsonb '[1,2,3]' @* '$[last]'; ?column? ---------- @@ -1871,3 +1909,61 @@ select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'; {"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 '{"a": 1}' @* '$["a"]'; + ?column? +---------- + 1 +(1 row) + +select jsonb '{"a": 1}' @* 'strict $["b"]'; +ERROR: SQL/JSON member not found +select jsonb '{"a": 1}' @* 'lax $["b"]'; + ?column? +---------- +(0 rows) + +select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]'; + ?column? +------------------ + 2 + 2 + 1 + {"a": 1, "b": 2} +(4 rows) + +select jsonb 'null' @* '{"a": 1}["a"]'; + ?column? +---------- + 1 +(1 row) + +select jsonb 'null' @* '{"a": 1}["b"]'; + ?column? +---------- +(0 rows) + diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql index 78677046ff..cf0e506fd5 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]'; select jsonb '[1]' @? '$[0.9]'; select jsonb '[1]' @? '$[1.2]'; select jsonb '[1]' @? 'strict $[1.2]'; +select jsonb '[1]' @* 'strict $[1.2]'; +select jsonb '{}' @* 'strict $[0.3]'; +select jsonb '{}' @? 'lax $[0.3]'; +select jsonb '{}' @* 'strict $[1.2]'; +select jsonb '{}' @? 'lax $[1.2]'; +select jsonb '{}' @* '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[*])'; @@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]'; select jsonb '1' @* 'lax $[0]'; select jsonb '1' @* 'lax $[*]'; +select jsonb '{}' @* 'lax $[0]'; select jsonb '[1]' @* 'lax $[0]'; select jsonb '[1]' @* 'lax $[*]'; select jsonb '[1,2,3]' @* 'lax $[*]'; select jsonb '[]' @* '$[last]'; select jsonb '[]' @* 'strict $[last]'; select jsonb '[1]' @* '$[last]'; +select jsonb '{}' @* 'lax $[last]'; select jsonb '[1,2,3]' @* '$[last]'; select jsonb '[1,2,3]' @* '$[last - 1]'; select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]'; @@ -415,3 +425,17 @@ select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*'; select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'; select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}'; select jsonb '[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 '{"a": 1}' @* '$["a"]'; +select jsonb '{"a": 1}' @* 'strict $["b"]'; +select jsonb '{"a": 1}' @* 'lax $["b"]'; +select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]'; + +select jsonb 'null' @* '{"a": 1}["a"]'; +select jsonb 'null' @* '{"a": 1}["b"]'; From 121839f38e1233a80112a1cc88e194d01cd3fc1e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 16:06:21 +0300 Subject: [PATCH 94/97] Add json tests for jsonpath extensions --- src/test/regress/expected/json_jsonpath.out | 222 ++++++++++++++++++++ src/test/regress/sql/json_jsonpath.sql | 47 +++++ 2 files changed, 269 insertions(+) diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out index eb002ddcdd..0afcefa98a 100644 --- a/src/test/regress/expected/json_jsonpath.out +++ b/src/test/regress/expected/json_jsonpath.out @@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function select json '""' @* '$.datetime()'; ERROR: Invalid argument for SQL/JSON datetime function +-- Standard extension: UNIX epoch to timestamptz +select json '0' @* '$.datetime()'; + ?column? +----------------------------- + "1970-01-01T00:00:00+00:00" +(1 row) + +select json '0' @* '$.datetime().type()'; + ?column? +---------------------------- + "timestamp with time zone" +(1 row) + +select json '1490216035.5' @* '$.datetime()'; + ?column? +------------------------------- + "2017-03-22T20:53:55.5+00:00" +(1 row) + select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; ?column? -------------- @@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; ---------- (0 rows) +SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]'; + ?column? +---------- + [1, 2] +(1 row) + SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a'; ?column? ---------- @@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; (1 row) +SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]'; + ?column? +---------- + [1, 2] +(1 row) + SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)'; ?column? ---------- @@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; f (1 row) +-- extension: path sequences +select json '[1,2,3,4,5]' @* '10, 20, $[*], 30'; + ?column? +---------- + 10 + 20 + 1 + 2 + 3 + 4 + 5 + 30 +(8 rows) + +select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30'; + ?column? +---------- + 10 + 20 + 30 +(3 rows) + +select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30'; +ERROR: SQL/JSON member not found +select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)'; + ?column? +---------- + -10 + -20 + -2 + -3 + -4 + -30 +(6 rows) + +select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()'; + ?column? +---------- + 10 + 20.5 + 2 + 3 + 4 + 30 +(6 rows) + +select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; + ?column? +---------- + 4 +(1 row) + +select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]'; +ERROR: Invalid SQL/JSON subscript +-- extension: array constructors +select json '[1, 2, 3]' @* '[]'; + ?column? +---------- + [] +(1 row) + +select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]'; + ?column? +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]'; + ?column? +---------- + 1 + 2 + 1 + 2 + 3 + 4 + 5 +(7 rows) + +select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]'; + ?column? +----------------------- + [1, 2, 1, 2, 3, 4, 5] +(1 row) + +select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'; + ?column? +------------------------------- + [[1, 2], [1, 2, 3, 4], 5, []] +(1 row) + +select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]'; +ERROR: SQL/JSON member not found +select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]'; + ?column? +-------------- + [4, 5, 6, 7] +(1 row) + +-- extension: object constructors +select json '[1, 2, 3]' @* '{}'; + ?column? +---------- + {} +(1 row) + +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}'; + ?column? +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*'; + ?column? +----------------- + 5 + [1, 2, 3, 4, 5] +(2 rows) + +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'; + ?column? +-------------------------------- + {"a": 5, "b": [1, 2, 3, 4, 5]} +(1 row) + +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}'; +ERROR: Singleton SQL/JSON item required +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}'; + ?column? +--------------------------------------------------------- + {"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 '{"a": 1}' @* '$["a"]'; + ?column? +---------- + 1 +(1 row) + +select json '{"a": 1}' @* 'strict $["b"]'; +ERROR: SQL/JSON member not found +select json '{"a": 1}' @* 'lax $["b"]'; + ?column? +---------- +(0 rows) + +select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]'; + ?column? +------------------ + 2 + 2 + 1 + {"a": 1, "b": 2} +(4 rows) + +select json 'null' @* '{"a": 1}["a"]'; + ?column? +---------- + 1 +(1 row) + +select json 'null' @* '{"a": 1}["b"]'; + ?column? +---------- +(0 rows) + diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql index 22428ccc5b..736a8b6e99 100644 --- a/src/test/regress/sql/json_jsonpath.sql +++ b/src/test/regress/sql/json_jsonpath.sql @@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()'; select json '{}' @* '$.datetime()'; select json '""' @* '$.datetime()'; +-- Standard extension: UNIX epoch to timestamptz +select json '0' @* '$.datetime()'; +select json '0' @* '$.datetime().type()'; +select json '1490216035.5' @* '$.datetime()'; + select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")'; select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()'; select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")'; @@ -367,13 +372,55 @@ set time zone default; SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]'; SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)'; +SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]'; SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a'; SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)'; SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)'; +SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]'; SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)'; SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)'; SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1'; SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2'; + +-- extension: path sequences +select json '[1,2,3,4,5]' @* '10, 20, $[*], 30'; +select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30'; +select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30'; +select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)'; +select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()'; +select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]'; +select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]'; + +-- extension: array constructors +select json '[1, 2, 3]' @* '[]'; +select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]'; +select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]'; +select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]'; +select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]'; +select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]'; +select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]'; + +-- extension: object constructors +select json '[1, 2, 3]' @* '{}'; +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}'; +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*'; +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]'; +select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}'; +select json '[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 '{"a": 1}' @* '$["a"]'; +select json '{"a": 1}' @* 'strict $["b"]'; +select json '{"a": 1}' @* 'lax $["b"]'; +select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]'; + +select json 'null' @* '{"a": 1}["a"]'; +select json 'null' @* '{"a": 1}["b"]'; From 4578fd5bc3ce9000e06cf7494b6e975c46653eb9 Mon Sep 17 00:00:00 2001 From: Liudmila Mantrova Date: Thu, 14 Jun 2018 14:44:41 +0300 Subject: [PATCH 95/97] first draft for SQL/JSON documentation --- doc/src/sgml/biblio.sgml | 11 + doc/src/sgml/datatype.sgml | 6 + doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/func-sqljson.sgml | 3510 ++++++++++++++++++++++++++++++++ doc/src/sgml/func.sgml | 877 +------- doc/src/sgml/gin.sgml | 4 + doc/src/sgml/json.sgml | 211 +- 7 files changed, 3742 insertions(+), 878 deletions(-) create mode 100644 doc/src/sgml/func-sqljson.sgml diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml index 4953024162..a7771dc139 100644 --- a/doc/src/sgml/biblio.sgml +++ b/doc/src/sgml/biblio.sgml @@ -136,6 +136,17 @@ 1988 + + SQL Technical Report + Part 6: SQL support for JavaScript Object + Notation (JSON) + First Edition. + + . + + 2017. + + diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 67bae32287..ce58bab4e5 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -148,6 +148,12 @@ binary JSON data, decomposed + + jsonpath + + binary JSON path + + line diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index f010cd4c3b..a60de6ca5a 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -18,6 +18,7 @@ + diff --git a/doc/src/sgml/func-sqljson.sgml b/doc/src/sgml/func-sqljson.sgml new file mode 100644 index 0000000000..e5693081a9 --- /dev/null +++ b/doc/src/sgml/func-sqljson.sgml @@ -0,0 +1,3510 @@ + + + + JSON Functions, Operators and Expressions + + + The functions, operators, and expressions described in this section + operate on JSON data: + + + + + + SQL/JSON functions and expressions conforming to the + SQL/JSON standard (see ). + + + + + PostgreSQL-specific functions and operators for JSON + data types (see ). + + + + + + To learn more about the SQL/JSON standard, see . + For details on JSON types supported in PostgreSQL, see . + + + + SQL/JSON Functions and Expressions + + SQL/JSON + functions and expressions + + + + To provide native support for JSON data types within the SQL environment, + PostgreSQL implements the SQL/JSON data model. + This model comprises sequences of items. Each item can hold SQL scalar values, + with an additional SQL/JSON null value, and composite data structures that use JSON + arrays and objects. + + + + SQL/JSON enables you to handle JSON data alongside regular SQL data, + with transaction support: + + + + + + Upload JSON data into a relational database and store it in + regular SQL columns as character or binary strings. + + + + + Generate JSON objects and arrays from relational data. + + + + + Query JSON data using SQL/JSON query functions and SQL/JSON path + language expressions. + + + + + + All SQL/JSON functions fall into one of the two groups. + Constructor functions + generate JSON data from values of SQL types. Query functions + evaluate SQL/JSON path language expressions against JSON values + and produce values of SQL/JSON types, which are converted to SQL types. + + + + Producing JSON Content + + + PostgreSQL provides several functions + that generate JSON data. Taking values of SQL types as input, these + functions construct JSON objects or JSON arrays represented as + SQL character or binary strings. + + + + + + + + + + + + + + + + + + + + + + + + + + + + JSON_OBJECT + create a JSON object + + + + +JSON_OBJECT ( +[ { key_expression { VALUE | ':' } + value_expression [ FORMAT JSON [ ENCODING UTF8 ] ] }[, ...] ] +[ { NULL | ABSENT } ON NULL ] +[ { WITH | WITHOUT } UNIQUE [ KEYS ] ] +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +) + + + + + + Description + + + JSON_OBJECT function generates a JSON + object from SQL or JSON data. + + + + + Parameters + + + + + key_expression { VALUE | ':' } + value_expression [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The input clause that provides the data for constructing a JSON object: + + + + + key_expression is a scalar expression defining the + JSON key, which is implicitly converted + to the text type. + The provided expression cannot be NULL or belong to a type that has a cast to json. + + + + + value_expression is an expression + that provides the input for the JSON value. + + + + + The optional FORMAT clause is provided to conform to the SQL/JSON standard. + + + + + You must use a colon or the VALUE keyword as a delimiter between + the key and the value. Multiple key/value pairs are separated by commas. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed + JSON object: + + + + NULL + + + Default. NULL values are allowed. + + + + + ABSENT + + + If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE [ KEYS ] + + + Defines whether duplicate keys are allowed: + + + + WITHOUT + + + Default. The constructed + JSON object can contain duplicate keys. + + + + + WITH + + + Duplicate keys are not allowed. + If the input data contains duplicate keys, an error is returned. + This check is performed before removing JSON items with NULL values. + + + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the type of the generated JSON object. + For details, see . + + + + + + + + + Notes + Alternatively, you can construct JSON objects by using + PostgreSQL-specific json_build_object()/ + jsonb_build_object() functions. + See for details. + + + + + Examples + + Construct a JSON object from the provided key/value pairs of various types: + + +SELECT JSON_OBJECT( +-- scalar JSON types + 'key1': 'string', + 'key2': '[1, 2]', + 'key3' VALUE 123, -- alternative syntax for key-value delimiter + 'key4': NULL, +-- other types + 'key5': ARRAY[1, 2, 3], -- postgres array + 'key6': jsonb '{"a": ["b", 1]}', -- composite json/jsonb + 'key7': date '2017-09-30', -- datetime type + 'key8': row(1, 'a'), -- row type + 'key9': '[1, 2]' FORMAT JSON, -- same value as for key2, but with FORMAT +-- key can be an expression + 'key' || 'last' : TRUE +ABSENT ON NULL) AS json; + json +---------------------------------------------------- +{"key1" : "string", "key2" : "[1, 2]", "key3" : 123, + "key5" : [1,2,3], "key6" : {"a": ["b", 1]}, + "key7" : "2017-09-30", "key8" : {"f1":1,"f2":"a"}, + "key9" : [1, 2], "keylast" : true} +(1 row) + + + + From the films table, select some data + about the films distributed by Paramount Pictures + (did = 103) and return JSON objects: + + +SELECT +JSON_OBJECT( + 'code' VALUE f.code, + 'title' VALUE f.title, + 'did' VALUE f.did +) AS paramount +FROM films AS f +WHERE f.did = 103; + paramount +---------------------------------------------------- +{"code" : "P_301", "title" : "Vertigo", "did" : 103} +{"code" : "P_302", "title" : "Becket", "did" : 103} +{"code" : "P_303", "title" : "48 Hrs", "did" : 103} +(3 rows) + + + + + + + JSON_OBJECTAGG + create a JSON object as an aggregate of the provided data + + + +JSON_OBJECTAGG ( +[ { key_expression { VALUE | ':' } value_expression } ] +[ { NULL | ABSENT } ON NULL ] +[ { WITH | WITHOUT } UNIQUE [ KEYS ] ] +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +) + + + + + + Description + + + JSON_OBJECTAGG function aggregates the provided data + into a JSON object. You can use this function to combine values + stored in different table columns into pairs. If you specify a GROUP BY + or an ORDER BY clause, this function returns a separate JSON object + for each table row. + + + + + Parameters + + + + + key_expression { VALUE | ':' } value_expression + + + + + The input clause that provides the data to be aggregated as a JSON object: + + + + + key_expression is a scalar expression defining the + JSON key, which is implicitly converted + to the text type. + The provided expression cannot be NULL or belong to a type that has a cast to json. + + + + + value_expression is an expression + that provides the input for the JSON value preceded by its type. + For JSON scalar types, you can omit the type. + + + + The input value of the bytea type must be stored in UTF8 + and contain a valid UTF8 string. Otherwise, an error occurs. + PostgreSQL currently supports only UTF8. + + + + + + You must use a colon or the VALUE keyword as a delimiter between + keys and values. Multiple key/value pairs are separated by commas. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed + JSON object: + + + + NULL + + + Default. NULL values are allowed. + + + + + ABSENT + + + If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE [ KEYS ] + + + Defines whether duplicate keys are allowed: + + + + WITHOUT + + + Default. The constructed + JSON object can contain duplicate keys. + + + + + WITH + + + Duplicate keys are not allowed. + If the input data contains duplicate keys, an error is returned. + This check is performed before removing JSON items with NULL values. + + + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the type of the generated JSON object. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON objects by using + PostgreSQL-specific json_object_agg()/ + jsonb_object_agg() aggregate functions. + See for details. + + + + + Examples + + + For films with did = 103, aggregate key/value pairs + of film genre (f.kind) and title (f.title) + into a single object: + + +SELECT +JSON_OBJECTAGG( + f.kind VALUE f.title) + AS films_list +FROM films AS f +where f.did = 103; + films_list +---------------------------------------------------- +{ "Action" : "Vertigo", "Drama" : "Becket", "Action" : "48 Hrs" } + + + + Return the same object as jsonb. Note that only a single film of + the action genre is included as the jsonb type does not allow duplicate keys. + + +SELECT +JSON_OBJECTAGG( + f.kind VALUE f.title + RETURNING jsonb) +AS films_list +FROM films AS f +where f.did = 103; + films_list +---------------------------------------------------- +{"Drama": "Becket", "Action": "48 Hrs"} + + + + Return objects of film titles and length, grouped by the film genre: + + +SELECT + f.kind, + JSON_OBJECTAGG( + f.title VALUE f.len +) AS films_list +FROM films AS f +GROUP BY f.kind; + + kind | films_list +-------------+---------------------------------- +Musical | { "West Side Story" : "02:32:00", "The King and I" : "02:13:00", "Bed Knobs and Broomsticks" : "01:57:00" } +Romantic | { "The African Queen" : "01:43:00", "Une Femme est une Femme" : "01:25:00", "Storia di una donna" : "01:30:00" } +Comedy | { "Bananas" : "01:22:00", "There's a Girl in my Soup" : "01:36:00" } +Drama | { "The Third Man" : "01:44:00", "Becket" : "02:28:00", "War and Peace" : "05:57:00", "Yojimbo" : "01:50:00", "Das Boot" : "02:29:00" } +Action | { "Vertigo" : "02:08:00", "48 Hrs" : "01:37:00", "Taxi Driver" : "01:54:00", "Absence of Malice" : "01:55:00" } +(5 rows) + + + + + + + JSON_ARRAY + create a JSON array + + + +JSON_ARRAY ( +[ { value_expression [ FORMAT JSON ] } [, ...] ] +[ { NULL | ABSENT } ON NULL ] +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +) + +JSON_ARRAY ( +[ query_expression ] +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +) + + + + + Description + + + JSON_ARRAY function constructs a JSON array from + the provided SQL or JSON data. + + + + + Parameters + + + + + value_expression + + + + + The input clause that provides the data for constructing a JSON array. + The value_expression is an expression + that provides the input for the JSON value preceded by its type. + For JSON scalar types, you can omit the type. + + + + The input value of the bytea type must be stored in UTF8 + and contain a valid UTF8 string. Otherwise, an error occurs. + PostgreSQL currently supports only UTF8. + + + + + + + + + query_expression + + + + An SQL query that provides the data for constructing a JSON array. + The query must return a single column that holds the values to be + used in the array. + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the generated JSON array: + + + + NULL + + + NULL values are allowed. + + + + + ABSENT + + + Default. If the value is NULL, + the corresponding key/value pair is omitted from the generated + JSON object. + + + + + + This clause is only supported for arrays built from an explicit list of values. + If you are using an SQL query to generate an array, NULL values are always + omitted. + + + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the return type of the constructed JSON array. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON arrays by using + PostgreSQL-specific json_build_array()/ + jsonb_build_array() functions. + See for details. + + + + + Examples + + From the films table, select some data + about the films distributed by Paramount Pictures + (did = 103) and return JSON arrays: + + +SELECT +JSON_ARRAY( + f.code, + f.title, + f.did +) AS films +FROM films AS f +WHERE f.did = 103; + films +---------------------------------------------------- +["code" : "P_301", "title" : "Vertigo", "did" : 103] +["code" : "P_302", "title" : "Becket", "did" : 103] +["code" : "P_303", "title" : "48 Hrs", "did" : 103] +(3 rows) + + + Construct a JSON array from the list of film titles returned from the + films table by a subquery: + + +SELECT +JSON_ARRAY( + SELECT + f.title +FROM films AS f +where f.did = 103) +AS film_titles; + film_titles +---------------------------------------------------- +["Vertigo", "Becket", "48 Hrs"] +(1 row) + + + + + + + JSON_ARRAYAGG + aggregate a JSON array + + + +JSON_ARRAYAGG ( +[ value_expression ] +[ ORDER BY sort_expression ] +[ { NULL | ABSENT } ON NULL ] +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +) + + + + + + Description + + + JSON_ARRAYAGG function aggregates the provided SQL + or JSON data into a JSON array. + + + + + Parameters + + + + + value_expression + + + + + The input clause that provides the input data to be aggregated as a JSON array. + The value_expression + can be a value or a query returning the values to be used as input in array construction. + You can provide multiple input values separated by commas. + + + + + + + ORDER BY + + + + Sorts the input data to be aggregated as a JSON array. + For details on the exact syntax of the ORDER BY clause, see . + + + + + + + { NULL | ABSENT } ON NULL + + + + Defines whether NULL values are allowed in the constructed array: + + + + NULLNULL values are allowed. + + + + + ABSENT (default) — NULL + values are omitted from the generated array. + + + + + + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the return type of the constructed JSON array. + For details, see . + + + + + + + + + Notes + Alternatively, you can create JSON arrays by using + PostgreSQL-specific json_agg()/ + jsonb_agg() functions. + See for details. + + + + + Examples + + Construct an array of film titles sorted in alphabetical order: + + +SELECT +JSON_ARRAYAGG( + f.title +ORDER BY f.title ASC) AS film_titles +FROM films AS f; + film_titles +---------------------------------------------------- +["48 Hrs", "Absence of Malice", "Bananas", "Becket", "Bed Knobs and Broomsticks", "Das Boot", "Storia di una donna", "Taxi Driver", "The African Queen", "The King and I", "There's a Girl in my Soup", "The Third Man", "Une Femme est une Femme", "Vertigo", "War and Peace", "West Side Story", "Yojimbo"] +(1 row) + + + + + + + Querying JSON + + + SQL/JSON query functions evaluate SQL/JSON path language expressions + against JSON values, producing values of SQL/JSON types, which are + converted to SQL types. All SQL/JSON query functions accept several + common clauses described in . + For details on the SQL/JSON path language, + see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + In some usage examples for these functions, + the following small table storing some JSON data will be used: + +CREATE TABLE my_films ( + js text ); + +INSERT INTO my_films VALUES ( +'{ "favorites" : [ + { "kind" : "comedy", "films" : [ + { "title" : "Bananas", + "director" : "Woody Allen"}, + { "title" : "The Dinner Game", + "director" : "Francis Veber" } ] }, + { "kind" : "horror", "films" : [ + { "title" : "Psycho", + "director" : "Alfred Hitchcock" } ] }, + { "kind" : "thriller", "films" : [ + { "title" : "Vertigo", + "director" : "Hitchcock" } ] }, + { "films" : [ + { "title" : "Yojimbo", + "director" : "Akira Kurosawa" } ] } + ] }'); + + + + + + JSON_EXISTS + check whether a JSON path expression can return any SQL/JSON items + + + +JSON_EXISTS ( + json_api_common_syntax +[ { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR ] +) + + + + + Description + + + JSON_EXISTS function checks whether the provided + JSON path expression can return any SQL/JSON items. + + + + + Parameters + + + + json_api_common_syntax + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + See for details. + + + + + + + { TRUE | FALSE | UNKNOWN | ERROR } ON ERROR + + + + Defines the return value if an error occurs. The default value is FALSE. + + + + + + + + + Examples + + + Check whether the provided jsonb data contains a + key/value pair with the key1 key, and its value + contains an array with one or more elements bigger than 2: + + +SELECT JSON_EXISTS(jsonb '{"key1": [1,2,3]}', 'strict $.key1[*] ? (@ > 2)'); + json_exists +------------- + t +(1 row) + + + + Note the difference between strict and lax modes + if the required item does not exist: + + +-- Strict mode with ERROR on ERROR clause +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR); +ERROR: Invalid SQL/JSON subscript +(1 row) + + + +-- Lax mode +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'lax $.a[5]' ERROR ON ERROR); + json_exists +------------- + f +(1 row) + + + +-- Strict mode using the default value for the ON ERROR clause +SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]'); + json_exists +------------- + f +(1 row) + + + + + + + + JSON_VALUE + extract a value from JSON data and convert + it to an SQL scalar + + + +JSON_VALUE ( + json_api_common_syntax +[ RETURNING data_type ] +[ { ERROR | NULL | DEFAULT expression } ON EMPTY ] +[ { ERROR | NULL | DEFAULT expression } ON ERROR ] +) + + + + + Description + + + JSON_VALUE function extracts a value from the provided + JSON data and converts it to an SQL scalar. + If the specified JSON path expression returns more than one + SQL/JSON item, an error occurs. To extract + an SQL/JSON array or object, use . + + + + + Parameters + + + + + + json_api_common_syntax + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + For details, see . + + + + + + + RETURNING data_type + + + + The output clause that specifies the data type of the returned value. + Out of the box, PostgreSQL + supports the following types: json, jsonb, + bytea, and character string types (text, char, + varchar, and nchar). + The extracted value must be a single SQL/JSON scalar item + and have a cast to the specified type. Otherwise, an error occurs. + By default, JSON_VALUE returns a string + of the text type. + + + + + + + { ERROR | NULL | DEFAULT expression } ON EMPTY + + + + Defines the return value if no JSON value is found. The default is NULL. + If you use DEFAULT expression, the provided + expression is evaluated and cast to the type specified in the + RETURNING clause. + + + + + + + { ERROR | NULL | DEFAULT expression } ON ERROR + + + + Defines the return value if an unhandled error occurs. The default is NULL. + If you use DEFAULT expression, the provided + expression is evaluated and cast to the type specified in the + RETURNING clause. + + + + + + + + + Examples + + + Extract an SQL/JSON value and return it as an SQL + scalar of the specified type. Note that + JSON_VALUE can only return a + single scalar, and the returned value must have a + cast to the specified return type: + + + +SELECT JSON_VALUE('"123.45"', '$' RETURNING float); + json_value +------------ + 123.45 +(1 row) + +SELECT JSON_VALUE('123.45', '$' RETURNING int ERROR ON ERROR); + json_value +------------ + 123 +(1 row) + +SELECT JSON_VALUE('"03:04 2015-02-01"', '$.datetime("HH24:MI YYYY-MM-DD")' RETURNING date); + json_value +------------ + 2015-02-01 +(1 row) + +SELECT JSON_VALUE('"123.45"', '$' RETURNING int ERROR ON ERROR); +ERROR: invalid input syntax for integer: "123.45" + +SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item + + + + If the path expression returns an array, an object, or + multiple SQL/JSON items, an error is returned, as specified + in the ON ERROR clause: + + +SELECT JSON_VALUE(jsonb '[1]', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '{"a": 1}', 'strict $' ERROR ON ERROR); +ERROR: SQL/JSON scalar required + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' ERROR ON ERROR); +ERROR: more than one SQL/JSON item + +SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 1 ON ERROR); +1 + + + + + + + + JSON_QUERY + extract an SQL/JSON array or object from JSON data + and return a JSON string + + + +JSON_QUERY ( + json_api_common_syntax +[ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] +[ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } [ ARRAY ] WRAPPER ] +[ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ] +[ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON EMPTY ] +[ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON ERROR ] +) + + + + + Description + + + JSON_QUERY function extracts an SQL/JSON + array or object from JSON data. This function must return + a JSON string, so if the path expression returns a scalar or multiple SQL/JSON + items, you must wrap the result using the WITH WRAPPER clause + or enclose the path expression into square brackets for automatic wrapping. + To extract a single SQL/JSON value, you can use . + + + + + Parameters + + + + + + json_api_common_syntax + + + + + The input data to query, the JSON path expression defining the query, and an optional PASSING clause. + For details, see . + + + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the data type of the returned value. + For details, see . + + + + + + + WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } [ ARRAY ] WRAPPER + + + + Defines whether to wrap a returned result as an array. + + + + WITH CONDITIONAL WRAPPER + + + Wrap the results if the path + expression returns anything other than a singleton SQL/JSON array or object. + + + + + WITH UNCONDITIONAL WRAPPER + + + Always wrap the result. + This is the default behavior if WITH WRAPPER is + specified. + + + + + WITHOUT WRAPPER + + + Do not wrap the result. + This is the default behavior if the WRAPPER + clause is omitted. + + + + + + Optionally, you can add the ARRAY keyword for semantic clarity. + + + You cannot use this clause together with the ON EMPTY clause. + + + + + + + + { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] + + + + Defines whether to keep or omit quotes if a scalar string is returned. + By default, scalar strings are returned with quotes. Using this + clause together with the WITH WRAPPER clause is not allowed. + + + Optionally, you can add the ON SCALAR STRING keywords for semantic clarity. + + + + + + + { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON EMPTY + + + + Defines the return value if no JSON value is found. The default is NULL. + If you use EMPTY ARRAY or EMPTY OBJECT, + an empty JSON array [] or object {} is returned, respectively. + You cannot use this clause together with the WRAPPER clause. + + + + + + + { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON ERROR + + + + Defines the return value if an unhandled error occurs. The default is NULL. + If you use EMPTY ARRAY or EMPTY OBJECT, + an empty JSON array [] or object {} are returned, respectively. + + + + + + + + + Examples + + + Extract all film genres listed in the my_films table: + + +SELECT + JSON_QUERY(js, '$.favorites[*].kind' WITH WRAPPER ERROR ON ERROR) +FROM my_films; + json_query +------------ + ["comedy", "horror", "thriller"] +(1 row) + + + + Note that the same query will result in an error if you omit the + WITH WRAPPER clause, as it returns multiple SQL/JSON items: + + +SELECT + JSON_QUERY(js, '$.favorites[*].kind' ERROR ON ERROR) +FROM my_films; +ERROR: more than one SQL/JSON item + + + + Compare the effect of different WRAPPER clauses: + + +SELECT + js, + JSON_QUERY(js, 'lax $[*]') AS "without", + JSON_QUERY(js, 'lax $[*]' WITH WRAPPER) AS "with uncond", + JSON_QUERY(js, 'lax $[*]' WITH CONDITIONAL WRAPPER) AS "with cond" +FROM + (VALUES (jsonb '[]'), ('[1]'), ('[[1,2,3]]'), ('[{"a": 1}]'), ('[1, null, "2"]')) foo(js); + js | without | with uncond | with cond +----------------+-----------+----------------+---------------- + [] | (null) | (null) | (null) + [1] | 1 | [1] | [1] + [[1, 2, 3]] | [1, 2, 3] | [[1, 2, 3]] | [1, 2, 3] + [{"a": 1}] | {"a": 1} | [{"a": 1}] | {"a": 1} + [1, null, "2"] | (null) | [1, null, "2"] | [1, null, "2"] +(5 rows) + + +Compare quote handling for scalar types with and without the OMIT QUOTES clause: + + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text); + json_query +------------ + "aaa" +(1 row) + +SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); + json_query +------------ + aaa +(1 row) + + + + + + + JSON_TABLE + display JSON data as an SQL relation + + + + +JSON_TABLE ( + json_api_common_syntax [ AS json_path_name ] + COLUMNS ( json_table_column [, ...] ) + [ PLAN ( json_table_plan ) | + PLAN DEFAULT ( { INNER | OUTER } [ , { CROSS | UNION } ] + | { CROSS | UNION } [ , { INNER | OUTER } ] ) + ] +) + +where json_table_column is: + + { name type [ PATH json_path_specification ] + [ { ERROR | NULL | DEFAULT expression } ON EMPTY ] + [ { ERROR | NULL | DEFAULT expression } ON ERROR ] + | name type FORMAT json_representation + [ PATH json_path_specification ] + [ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } + [ ARRAY ] WRAPPER ] + [ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ] + [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON EMPTY ] + [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON ERROR ] + | NESTED PATH json_path_specification [ AS path_name ] + COLUMNS ( json_table_column [, ...] ) + | name FOR ORDINALITY } + +json_table_plan is: + + path_name { OUTER | INNER | CROSS | UNION } + { path_name | ( nested_plan ) } + +nested_plan is: + + path_name { OUTER | INNER } { path_name | ( nested_plan ) } + | { path_name | ( nested_plan ) } + { { UNION | CROSS } { path_name | ( nested_plan ) } }[...] + + + + + + Description + + + JSON_TABLE function queries JSON data + and presents the results as a relational view, which can be accessed as a + regular SQL table. You can only use JSON_TABLE inside the + FROM clause of the SELECT statement + for an SQL table. + + + + Taking JSON data as input, JSON_TABLE uses + a path expression to extract a part of the provided input that + will be used as a row pattern for the + contructed view. Each SQL/JSON item at the top level of the row pattern serves + as the source for a separate row in the constructed relational view. + + + + To split the row pattern into columns, JSON_TABLE + provides the COLUMNS clause that defines the + schema of the created view. For each column to be constructed, + this clause provides a separate path expression that evaluates + the row pattern, extracts a JSON item, and returns it as a + separate SQL value for the specified column. If the required value + is stored in a nested level of the row pattern, it can be extracted + using the NESTED PATH subclause. + + + + The rows produced by JSON_TABLE are laterally + joined to the row that generated them, so you do not have to explicitly join + the constructed view with the original table. You can specify + how to handle empty rows returned from the nested levels of + the row pattern using the PLAN clause. + + + + + + Parameters + + + + + json_api_common_syntax + + + + + The input data to query, the JSON path expression defining the query, + and an optional PASSING clause, as described in + . The result of the input data + evaluation is called the row pattern. The row + pattern is used as the source for row values in the constructed view. + + + + + + + COLUMNS( { json_table_column } [, ...] ) + + + + + The COLUMNS clause defining the schema of the + constructed table. In this clause, you must specify all the columns + to be filled with SQL/JSON items returned by JSON_TABLE. + You must provide the name and type for each column. Only scalar + types are supported. + The json_table_column + expression can use one of the following syntax options: + + + + + + name type + [ PATH json_path_specification ] + + + + + Inserts a single SQL/JSON item into each row of + the specified column. + + + The provided PATH expression parses the + row pattern defined by json_api_common_syntax + and fills the column with produced SQL/JSON items, one for each row. + If the PATH expression is omitted, + JSON_TABLE uses the + $.name path expression, + where name is the provided column name. + In this case, the column name must correspond to one of the + keys within the SQL/JSON item produced by the row pattern. + + + Optionally, you can add ON EMPTY and + ON ERROR clauses to define how to handle + missing values or structural errors. These clauses have the same syntax + and semantics as in . + + + + + + + name type FORMAT json_representation + [ PATH json_path_specification ] + + + + + Gerenates a column and inserts a composite SQL/JSON + item into each row of this column. + + + The provided PATH expression parses the + row pattern defined by json_api_common_syntax + and fills the column with produced SQL/JSON items, one for each row. + If the PATH expression is omitted, + JSON_TABLE uses the + $.name path expression, + where name is the provided column name. + In this case, the column name must correspond to one of the + keys within the SQL/JSON item produced by the row pattern. + + + Optionally, you can add WRAPPER, QUOTES, + ON EMPTY and ON ERROR clauses + to define additional settings for the returned SQL/JSON items. + These clauses have the same syntax and semantics as + in . + + + + + + + NESTED PATH json_path_specification [ AS json_path_name ] + COLUMNS ( json_table_column [, ...] ) + + + + + Extracts SQL/JSON items from nested levels of the row pattern, + gerenates one or more columns as defined by the COLUMNS + subclause, and inserts the extracted SQL/JSON items into each row of these columns. + The json_table_column expression in the + COLUMNS subclause uses the same syntax as in the + parent COLUMNS clause. + + + + The NESTED PATH syntax is recursive, + so you can go down multiple nested levels by specifying several + NESTED PATH subclauses within each other. + It allows to unnest the hierarchy of JSON objects and arrays + in a single function invocation rather than chaining several JSON_TABLE + expressions in an SQL statement. + + + + You can use the PLAN clause to define how + to handle empty rows when joining the columns returned by + NESTED PATH clauses. + + + + + + + name FOR ORDINALITY + + + + + Adds an ordinality column that provides sequential row numbering. + You can have only one ordinality column per table. Row numbering + is 1-based. + + + + + + + + + + + json_path_name + + + + + The optional json_path_name serves as an + identifier of the provided json_path_specification. + The path name must be unique and cannot coincide with column names. + You must specify the path name when using the PLAN + clause. + + + + + + + PLAN json_table_plan + + + + + Defines how to handle empty rows when joining the columns + returned by NESTED PATH clauses. + + + In relation to the columns returned directly from the row expression + or by the NESTED PATH clause of a higher level, nested columns + are considered to be child columns. + Each NESTED PATH clause can include + several columns, which are called sibling columns. + + + To join columns with parent/child relationship, you can use: + + + + + INNER + + + + + Use INNER JOIN, so that the output includes + only those rows that have the corresponding values in both + columns. + + + + + + + OUTER + + + + + Use LEFT OUTER JOIN, so that all rows of the + left-hand column must be included into the output at least once, while the rows of the right-hand + column are only included if they have a match in the left column. If the corresponding + value is missing from the right column, the NULL value is inserted into the right column. + + + This is the default option for joining columns with parent/child relationship. + + + + + + + To join sibling columns, you can use: + + + + + + UNION + + + + + Use FULL OUTER JOIN, so that all rows of the + left-hand and the right-hand columns are included into the output, with + NULL values inserted into either of the columns if the corresponding value + is missing. + + + This is the default option for joining sibling columns. + + + + + + + CROSS + + + + + Use CROSS JOIN, so that the output includes + a row for every possible combination of rows from the left-hand + and the right-hand columns. + + + + + + + + + + + + PLAN DEFAULT + + + + Redefines the default behavior for all columns at once. + + + + + + + + Examples + + + Query the my_films table holding + some JSON data about the films and create a view that + distributes the film genre, title, and director between separate columns: + +SELECT jt.* FROM + my_films, + JSON_TABLE ( js, '$.favorites[*]' COLUMNS ( + id FOR ORDINALITY, + kind text PATH '$.kind', + NESTED PATH '$.films[*]' COLUMNS ( + title text PATH '$.title', + director text PATH '$.director'))) AS jt; +----+----------+------------------+------------------- + id | kind | title | director +----+----------+------------------+------------------- + 1 | comedy | Bananas | Woody Allen + 1 | comedy | The Dinner Game | Francis Veber + 2 | horror | Psycho | Alfred Hitchcock + 3 | thriller | Vertigo | Hitchcock + 4 | (null) | Yojimbo | Akira Kurosawa + (5 rows) + + + + + + + + IS JSON + test whether the provided value is valid JSON data + + + + +expression + IS [ NOT ] JSON + [ { VALUE | SCALAR | ARRAY | OBJECT } ] + [ { WITH | WITHOUT } UNIQUE [ KEYS ] ] + + + + + Description + + + IS JSON predicate tests whether the provided value is valid + JSON data. If you provide a specific JSON data type as a parameter, + you can check whether the value belongs to this type. + You can also use this predicate in the IS NOT JSON form. + The return values are: + + + + t if the value satisfies the specified condition. + + + + + f if the value does not satisfy the specified condition. + + + + + + + + Parameters + + + + + + expression + + + + + The input clause defining the value to test. You can provide the values of json, + jsonb, bytea, or character string types. + + + + + + + VALUE | SCALAR | ARRAY | OBJECT + + + + + Specifies the JSON data type to test for: + + + + VALUE (default) — any JSON type. + + + + + SCALARJSON number, string, or boolean. + + + + + ARRAYJSON array. + + + + + OBJECTJSON object. + + + + + + + + + + { WITH | WITHOUT } UNIQUE [ KEYS ] + + + Defines whether duplicate keys are allowed: + + + + WITHOUT (default) — the + JSON object can contain duplicate keys. + + + + + WITH — duplicate keys are not allowed. + If the input data contains duplicate keys, it is considered to be invalid JSON. + + + + Optionally, you can add the KEYS keyword for semantic clarity. + + + + + + + + + Examples + + + Compare the result returned by the IS JSON + predicate for different data types: + + +SELECT + js, + js IS JSON "is json", + js IS NOT JSON "is not json", + js IS JSON SCALAR "is scalar", + js IS JSON OBJECT "is object", + js IS JSON ARRAY "is array" +FROM + (VALUES ('123'), ('"abc"'), ('{"a": "b"}'), ('[1,2]'), ('abc')) foo(js); + + js | is json | is not json | is scalar | is object | is array +------------+---------+-------------+-----------+-----------|------------- + 123 | t | f | t | f | f + "abc" | t | f | t | f | f + {"a": "b"} | t | f | f | t | f + [1,2] | t | f | f | f | t + abc | f | t | f | f | f +(5 rows) + + + + + + + SQL/JSON Common Clauses + + + SQL/JSON Input Clause + + + + + context_item, path_expression +[ PASSING { value AS varname } [, ...]] + + + + The input clause specifies the JSON data to query and + the exact query path to be passed to SQL/JSON query functions: + + + + + The context_item is the JSON data to query. + + + + + The path_expression is an SQL/JSON path + expression that specifies the items to be retrieved from the JSON + data. For details on path expression syntax, see + . + + + + + The optional PASSING clause provides the values for + the named variables used in the SQL/JSON path expression. + + + + + The input clause is common for all SQL/JSON query functions. + + + + + + + + + SQL/JSON Output Clause + + + + + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] + + + + The output clause that specifies the return type of the generated + JSON object. Out of the box, PostgreSQL + supports the following types: json, jsonb, + bytea, and character string types (text, char, + varchar, and nchar). + To use other types, you must create the CAST from json for this type. + By default, the json type is returned. + + + The optional FORMAT clause is provided to conform to the SQL/JSON standard. + + + The output clause is common for both constructor and query SQL/JSON functions. + + + + + + + + + + + + SQL/JSON Path Expressions + + + SQL/JSON path expressions specify the items to be retrieved + from the JSON data, which are passed to SQL/JSON query functions + as one of the parameters. Path expressions belong to a special + jsonpath type described in . + + + + The SQL/JSON query functions pass the provided path expression to + the path engine for evaluation. + The path expression is evaluated from left to right. + You can use parentheses to change the order of operations. + If the evaluation is successful, an SQL/JSON sequence is produced. + The evaluation result is returned to the SQL/JSON query function + that completes the specified computation. If the query result + must be JSON text, you have to use the WITH WRAPPER clause + or enclose the path expression in square brackets to ensure + the evaluation result is an array. + + + + A typical path expression has the following structure: + + + +'[strict | lax] path_specification [? (filter_expression) ...]' + + + + where: + + + + + + The optional strict or lax mode + defines how to handle structural errors, as explained in + . + + + + + The path_specification defines the parts + of JSON data to be retrieved by the SQL/JSON query functions. + To learn the syntax of the path specification, see + . + + + + + The optional filter_expression can include + one or more filtering conditions to apply to the result of the + path evaluation. For details, see . + + + + + + Strict and Lax Modes + + When you query JSON data, the path expression may not match the + actual JSON data structure. An attempt to access a non-existent + member of an object or element of an array results in a + structural error. SQL/JSON path expressions have two modes + of handling structural errors: + + + + + lax (default) — the path engine implicitly adapts + the queried data to the specified path. + Any remaining structural errors are suppressed and converted + to empty SQL/JSON sequences. + + + + + strict — if a structural error occurs, + an error is raised. + + + + + + The lax mode facilitates matching of a JSON document structure and path + expression if the JSON data does not conform to the expected schema. + If an operand does not match the requirements of a particular + operation, it can be automatically wrapped as an SQL/JSON array or unwrapped + by converting its elements into an SQL/JSON sequence before performing + this operation. Besides, comparison operators automatically unwrap their + operands in the lax mode, so you can compare SQL/JSON arrays out-of-the-box. + Arrays of size 1 are interchangeable with a singleton. + + + + + In the lax mode, implicit unwrapping only goes one level down. + If the arrays are nested, only the outermost array is unwrapped, + while all the inner arrays remain unchanged. + + + + + If you prefer using the strict mode, the specified path must exactly match + the structure of the queried JSON document. You can still get some + error-handling flexibility by using the JSON_EXISTS + predicate, which checks whether the element to be accessed is + available. It allows to convert structural errors to empty SQL/JSON + sequences on a selective basis, achieving lax semantics in the strict + mode as required. + + + + In both strict and lax modes, the actual interpretation of the returned value + depends on the ON ERROR or ON EMPTY + clauses of the SQL/JSON query functions, as explained in . + + + + + Path Specification + + + A path specification defines the exact path to access one or more items + within JSON data using the SQL/JSON path language. + + + + The general structure of a path specification is as follows: + + + + Each path specification starts with a $ sign, + which denotes the JSON text to be queried (the context item). + + + + + The context item can be followed + by one or more accessor operators. + Going down the JSON structure level by level, + these operators return an SQL/JSON sequence if path evaluation is successful. + + + + + Path evaluation results can be further processed by one or more jsonpath + operators and methods listed in . + Each method must be preceded by a dot, while arithmetic and boolean + operators are separated from the operands by spaces. + + + + + If the path specification is enclosed into square brackets [], + path evaluation result is automatically wrapped into an array. + This is a PostgreSQL extension of the SQL/JSON standard. + + + + + + + + + Consider the following path specification examples: + +'$.floor' +'($+1)' +'$+1' +'($.floor[*].apt[*].area > 10)' + + + + Writing the path as an expression is also a valid path specification: + +'$' || '.' || 'a' + + + + + If you use any named variables in the path specification, you must define + their values in the PASSING clause of the SQL/JSON query functions. + + + + + + Filter Clause + + + The optional filter clause is similar to the WHERE + clause in SQL. The filter clause can provide one or more + filter expressions, each including one or more + filtering conditions to apply to the result of the path evaluation. + Filter expressions are applied from left to right and can be nested. + The @ variable denotes the current item returned + by the path evaluation to which the filtering condition should be applied. + + + + Filter expressions must be enclosed in parentheses and preceded by + a question mark ?. Functions and operators that can be used in + filter expressions are listed in . + The result of the filter expression may be true, false, or unknown. + + + + + + + + SQL/JSON Path Operators and Methods + + + <type>jsonpath</type> Operators and Methods + + + + Operator/Method + Description + Example JSON + Example Query + Result + + + + + + (unary) + Plus operator that iterates over the json sequence + {"x": [2.85, -14.7, -9.4]} + + $.x.floor() + 2, -15, -10 + + + - (unary) + Minus operator that iterates over the json sequence + {"x": [2.85, -14.7, -9.4]} + - $.x.floor() + -2, 15, 10 + + + + (binary) + Addition + [2] + 2 + $[0] + 4 + + + - (binary) + Subtraction + [2] + 4 - $[0] + 2 + + + * + Multiplication + [4] + 2 * $[0] + 8 + + + / + Division + [8] + $[0] / 2 + 4 + + + % + Modulus + [32] + $[0] % 10 + 2 + + + type() + Type of the SQL/JSON item + [1, "2", {}] + $[*].type() + "number", "string", "object" + + + size() + Size of the SQL/JSON item + {"m": [11, 15]} + $.m.size() + 2 + + + double() + Approximate numeric value converted from a string + {"len": "1.9"} + $.len.double() * 2 + 3.8 + + + ceiling() + Nearest integer greater than or equal to the SQL/JSON number + {"h": 1.3} + $.h.ceiling() + 2 + + + floor() + Nearest integer less than or equal to the SQL/JSON number + {"h": 1.3} + $.h.floor() + 1 + + + abs() + Absolute value of the SQL/JSON number + {"z": -0.3} + $.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" + + + keyvalue() + Array of objects containing two members ("key" and "value" of the SQL/JSON item) + {"x": "20", "y": 32} + $.keyvalue() + {"key": "x", "value": "20"}, {"key": "y", "value": 32} + + + +
+ + <type>jsonpath</type> Filter Expression Elements + + + + Value/Predicate + Description + Example JSON + Example Query + Result + + + + + == + Equality operator + [1, 2, 1, 3] + $[*] ? (@ == 1) + 1, 1 + + + != + Non-equality operator + [1, 2, 1, 3] + $[*] ? (@ != 1) + 2, 3 + + + <> + Non-equality operator (same as !=) + [1, 2, 1, 3] + $[*] ? (@ <> 1) + 2, 3 + + + < + Less-than operator + [1, 2, 3] + $[*] ? (@ < 2) + 1, 2 + + + <= + Less-than-or-equal-to operator + [1, 2, 3] + $[*] ? (@ < 2) + 1 + + + > + Greater-than operator + [1, 2, 3] + $[*] ? (@ > 2) + 3 + + + > + Greater-than-or-equal-to operator + [1, 2, 3] + $[*] ? (@ >= 2) + 2, 3 + + + true + Value used to perform comparison with JSON true literal + [{"name": "John", "parent": false}, + {"name": "Chris", "parent": true}] + $[*] ? (@.parent == true) + {"name": "Chris", "parent": true} + + + false + Value used to perform comparison with JSON false literal + [{"name": "John", "parent": false}, + {"name": "Chris", "parent": true}] + $[*] ? (@.parent == false) + {"name": "John", "parent": false} + + + null + Value used to perform comparison with JSON null value + [{"name": "Mary", "job": null}, + {"name": "Michael", "job": "driver"}] + $[*] ? (@.job == null) .name + "Mary" + + + && + Boolean AND + [1, 3, 7] + $[*] ? (@ > 1 && @ < 5) + 3 + + + || + Boolean OR + [1, 3, 7] + $[*] ? (@ < 1 || @ > 5) + 7 + + + ! + Boolean NOT + [1, 3, 7] + $[*] ? (!(@ < 5)) + 7 + + + like_regex + Tests pattern matching with regular expressions + ["abc", "abd", "aBdC", "abdacb", "babc"] + $[*] ? (@ like_regex "^ab.*c" flag "i") + "abc", "aBdC", "abdacb" + + + starts with + Tests whether the second operand is an initial substring of the first operand + ["John Smith", "Mary Stone", "Bob Johnson"] + $[*] ? (@ starts with "John") + "John Smith" + + + exists + Tests whether a path expression has at least one SQL/JSON item + {"x": [1, 2], "y": [2, 4]} + strict $.* ? (exists (@ ? (@[*] > 2))) + 2, 4 + + + is unknown + Tests whether a boolean condition is unknown + [-1, 2, 7, "infinity"] + $[*] ? ((@ > 0) is unknown) + "infinity" + + + +
+ + + Extended <type>jsonpath</type> Methods + + + + Method + Description + Example JSON + Example Query + Result + + + + + min() + Minimum value in the json array + [1, 2, 0, 3, 1] + $.min() + 0 + + + max() + Maximum value in the json array + [1, 2, 0, 3, 1] + $.max() + 3 + + + map() + Calculate an expression by applying a given function + to each element of the json array + + [1, 2, 0] + $.map(@ * 2) + [2, 4, 0] + + + reduce() + Calculate an aggregate expression by combining elements + of the json array using a given function + ($1 references the current result, $2 references the current element) + + [3, 5, 9] + $.reduce($1 + $2) + 17 + + + fold() + Calculate an aggregate expression by combining elements + of the json array using a given function + with the specified initial value + ($1 references the current result, $2 references the current element) + + [2, 3, 4] + $.fold($1 * $2, 1) + 24 + + + foldl() + Calculate an aggregate expression by combining elements + of the json array using a given function from left to right + with the specified initial value + ($1 references the current result, $2 references the current element) + + [1, 2, 3] + $.foldl([$1, $2], []) + [[[[], 1], 2], 3] + + + foldr() + Calculate an aggregate expression by combining elements + of the json array using a given function from right to left + with the specified initial value + ($1 references the current result, $2 references the current element) + + [1, 2, 3] + $.foldr([$2, $1], []) + [[[[], 3], 2], 1] + + + +
+
+ +
+ + + PostgreSQL-specific JSON Functions and Operators + + + JSON + functions and operators + + + + shows the operators that + are available for use with JSON data types (see ). + + + + <type>json</type> and <type>jsonb</type> Operators + + + + Operator + Right Operand Type + Return type + Description + Example + Example Result + + + + + -> + int + json or jsonb + Get JSON array element (indexed from zero, negative + integers count from the end) + '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 + {"c":"baz"} + + + -> + text + json or jsonb + Get JSON object field by key + '{"a": {"b":"foo"}}'::json->'a' + {"b":"foo"} + + + ->> + int + text + Get JSON array element as text + '[1,2,3]'::json->>2 + 3 + + + ->> + text + text + Get JSON object field as text + '{"a":1,"b":2}'::json->>'b' + 2 + + + #> + text[] + json or jsonb + Get JSON object at the specified path + '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}' + {"c": "foo"} + + + #>> + text[] + text + Get JSON object at the specified path as text + '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' + 3 + + + @* + jsonpath + setof json or setof jsonb + Get all JSON items returned by JSON path for the specified JSON value + '{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)' + +3 +4 +5 + + + + @# + jsonpath + json or jsonb + Get all JSON items returned by JSON path for the specified JSON value. If there is more than one item, they will be wrapped into an array. + '{"a":[1,2,3,4,5]}'::json @# '$.a[*] ? (@ > 2)' + [3, 4, 5] + + + @? + jsonpath + boolean + Check whether JSON path returns any item for the specified JSON value + '{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)' + true + + + @~ + jsonpath + boolean + Get JSON path predicate result for the specified JSON value + '{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2' + true + + + +
+ + + + There are parallel variants of these operators for both the + json and jsonb types. + The field/element/path extraction operators + return the same type as their left-hand input (either json + or jsonb), except for those specified as + returning text, which coerce the value to text. + The field/element/path extraction operators return NULL, rather than + failing, if the JSON input does not have the right structure to match + the request; for example if no such element exists. The + field/element/path extraction operators that accept integer JSON + array subscripts all support negative subscripting from the end of + arrays. + + + + The standard comparison operators shown in are available for + jsonb, but not for json. They follow the + ordering rules for B-tree operations outlined at . + + + Some further operators also exist only for jsonb, as shown + in . + Many of these operators can be indexed by + jsonb operator classes. For a full description of + jsonb containment and existence semantics, see . + describes how these operators can be used to effectively index + jsonb data. + + + Additional <type>jsonb</type> Operators + + + + Operator + Right Operand Type + Description + Example + + + + + @> + jsonb + Does the left JSON value contain the right JSON + path/value entries at the top level? + '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb + + + <@ + jsonb + Are the left JSON path/value entries contained at the top level within + the right JSON value? + '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb + + + ? + text + Does the string exist as a top-level + key within the JSON value? + '{"a":1, "b":2}'::jsonb ? 'b' + + + ?| + text[] + Do any of these array strings + exist as top-level keys? + '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] + + + ?& + text[] + Do all of these array strings exist + as top-level keys? + '["a", "b"]'::jsonb ?& array['a', 'b'] + + + || + jsonb + Concatenate two jsonb values into a new jsonb value + '["a", "b"]'::jsonb || '["c", "d"]'::jsonb + + + - + text + Delete key/value pair or string + element from left operand. Key/value pairs are matched based + on their key value. + '{"a": "b"}'::jsonb - 'a' + + + - + text[] + Delete multiple key/value pairs or string + elements from left operand. Key/value pairs are matched based + on their key value. + '{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[] + + + - + integer + Delete the array element with specified index (Negative + integers count from the end). Throws an error if top level + container is not an array. + '["a", "b"]'::jsonb - 1 + + + #- + text[] + Delete the field or element with specified path (for + JSON arrays, negative integers count from the end) + '["a", {"b":1}]'::jsonb #- '{1,b}' + + + +
+ + + + The || operator concatenates the elements at the top level of + each of its operands. It does not operate recursively. For example, if + both operands are objects with a common key field name, the value of the + field in the result will just be the value from the right hand operand. + + + + + shows the functions that are + available for creating json and jsonb values. + (There are no equivalent functions for jsonb, of the row_to_json + and array_to_json functions. However, the to_jsonb + function supplies much the same functionality as these functions would.) + + + + to_json + + + array_to_json + + + row_to_json + + + json_build_array + + + json_build_object + + + json_object + + + to_jsonb + + + jsonb_build_array + + + jsonb_build_object + + + jsonb_object + + + + JSON Creation Functions + + + + Function + Description + Example + Example Result + + + + + to_json(anyelement) + to_jsonb(anyelement) + + + Returns the value as json or jsonb. + Arrays and composites are converted + (recursively) to arrays and objects; otherwise, if there is a cast + from the type to json, the cast function will be used to + perform the conversion; otherwise, a scalar value is produced. + For any scalar type other than a number, a Boolean, or a null value, + the text representation will be used, in such a fashion that it is a + valid json or jsonb value. + + to_json('Fred said "Hi."'::text) + "Fred said \"Hi.\"" + + + + array_to_json(anyarray [, pretty_bool]) + + + Returns the array as a JSON array. A PostgreSQL multidimensional array + becomes a JSON array of arrays. Line feeds will be added between + dimension-1 elements if pretty_bool is true. + + array_to_json('{{1,5},{99,100}}'::int[]) + [[1,5],[99,100]] + + + + row_to_json(record [, pretty_bool]) + + + Returns the row as a JSON object. Line feeds will be added between + level-1 elements if pretty_bool is true. + + row_to_json(row(1,'foo')) + {"f1":1,"f2":"foo"} + + + json_build_array(VARIADIC "any") + jsonb_build_array(VARIADIC "any") + + + Builds a possibly-heterogeneously-typed JSON array out of a variadic + argument list. + + json_build_array(1,2,'3',4,5) + [1, 2, "3", 4, 5] + + + json_build_object(VARIADIC "any") + jsonb_build_object(VARIADIC "any") + + + Builds a JSON object out of a variadic argument list. By + convention, the argument list consists of alternating + keys and values. + + json_build_object('foo',1,'bar',2) + {"foo": 1, "bar": 2} + + + json_object(text[]) + jsonb_object(text[]) + + + Builds a JSON object out of a text array. The array must have either + exactly one dimension with an even number of members, in which case + they are taken as alternating key/value pairs, or two dimensions + such that each inner array has exactly two elements, which + are taken as a key/value pair. + + json_object('{a, 1, b, "def", c, 3.5}') + json_object('{{a, 1},{b, "def"},{c, 3.5}}') + {"a": "1", "b": "def", "c": "3.5"} + + + json_object(keys text[], values text[]) + jsonb_object(keys text[], values text[]) + + + This form of json_object takes keys and values pairwise from two separate + arrays. In all other respects it is identical to the one-argument form. + + json_object('{a, b}', '{1,2}') + {"a": "1", "b": "2"} + + + +
+ + + + array_to_json and row_to_json have the same + behavior as to_json except for offering a pretty-printing + option. The behavior described for to_json likewise applies + to each individual value converted by the other JSON creation functions. + + + + + + The extension has a cast + from hstore to json, so that + hstore values converted via the JSON creation functions + will be represented as JSON objects, not as primitive string values. + + + + + shows the functions that + are available for processing json and jsonb values. + + + + json_array_length + + + jsonb_array_length + + + json_each + + + jsonb_each + + + json_each_text + + + jsonb_each_text + + + json_extract_path + + + jsonb_extract_path + + + json_extract_path_text + + + jsonb_extract_path_text + + + json_object_keys + + + jsonb_object_keys + + + json_populate_record + + + jsonb_populate_record + + + json_populate_recordset + + + jsonb_populate_recordset + + + json_array_elements + + + jsonb_array_elements + + + json_array_elements_text + + + jsonb_array_elements_text + + + json_typeof + + + jsonb_typeof + + + json_to_record + + + jsonb_to_record + + + json_to_recordset + + + jsonb_to_recordset + + + json_strip_nulls + + + jsonb_strip_nulls + + + jsonb_set + + + jsonb_insert + + + jsonb_pretty + + + + JSON Processing Functions + + + + Function + Return Type + Description + Example + Example Result + + + + + json_array_length(json) + jsonb_array_length(jsonb) + + int + + Returns the number of elements in the outermost JSON array. + + json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') + 5 + + + json_each(json) + jsonb_each(jsonb) + + setof key text, value json + setof key text, value jsonb + + + Expands the outermost JSON object into a set of key/value pairs. + + select * from json_each('{"a":"foo", "b":"bar"}') + + + key | value +-----+------- + a | "foo" + b | "bar" + + + + + json_each_text(json) + jsonb_each_text(jsonb) + + setof key text, value text + + Expands the outermost JSON object into a set of key/value pairs. The + returned values will be of type text. + + select * from json_each_text('{"a":"foo", "b":"bar"}') + + + key | value +-----+------- + a | foo + b | bar + + + + + json_extract_path(from_json json, VARIADIC path_elems text[]) + jsonb_extract_path(from_json jsonb, VARIADIC path_elems text[]) + + jsonjsonb + + + Returns JSON value pointed to by path_elems + (equivalent to #> operator). + + json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4') + {"f5":99,"f6":"foo"} + + + json_extract_path_text(from_json json, VARIADIC path_elems text[]) + jsonb_extract_path_text(from_json jsonb, VARIADIC path_elems text[]) + + text + + Returns JSON value pointed to by path_elems + as text + (equivalent to #>> operator). + + json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6') + foo + + + json_object_keys(json) + jsonb_object_keys(jsonb) + + setof text + + Returns set of keys in the outermost JSON object. + + json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') + + + json_object_keys +------------------ + f1 + f2 + + + + + json_populate_record(base anyelement, from_json json) + jsonb_populate_record(base anyelement, from_json jsonb) + + anyelement + + Expands the object in from_json to a row + whose columns match the record type defined by base + (see note below). + + select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c": {"d": 4, "e": "a b c"}}') + + + a | b | c +---+-----------+------------- + 1 | {2,"a b"} | (4,"a b c") + + + + + json_populate_recordset(base anyelement, from_json json) + jsonb_populate_recordset(base anyelement, from_json jsonb) + + setof anyelement + + Expands the outermost array of objects + in from_json to a set of rows whose + columns match the record type defined by base (see + note below). + + select * from json_populate_recordset(null::myrowtype, '[{"a":1,"b":2},{"a":3,"b":4}]') + + + a | b +---+--- + 1 | 2 + 3 | 4 + + + + + json_array_elements(json) + jsonb_array_elements(jsonb) + + setof json + setof jsonb + + + Expands a JSON array to a set of JSON values. + + select * from json_array_elements('[1,true, [2,false]]') + + + value +----------- + 1 + true + [2,false] + + + + + json_array_elements_text(json) + jsonb_array_elements_text(jsonb) + + setof text + + Expands a JSON array to a set of text values. + + select * from json_array_elements_text('["foo", "bar"]') + + + value +----------- + foo + bar + + + + + json_typeof(json) + jsonb_typeof(jsonb) + + text + + Returns the type of the outermost JSON value as a text string. + Possible types are + object, array, string, number, + boolean, and null. + + json_typeof('-123.4') + number + + + json_to_record(json) + jsonb_to_record(jsonb) + + record + + Builds an arbitrary record from a JSON object (see note below). As + with all functions returning record, the caller must + explicitly define the structure of the record with an AS + clause. + + select * from json_to_record('{"a":1,"b":[1,2,3],"c":[1,2,3],"e":"bar","r": {"a": 123, "b": "a b c"}}') as x(a int, b text, c int[], d text, r myrowtype) + + + a | b | c | d | r +---+---------+---------+---+--------------- + 1 | [1,2,3] | {1,2,3} | | (123,"a b c") + + + + + json_to_recordset(json) + jsonb_to_recordset(jsonb) + + setof record + + Builds an arbitrary set of records from a JSON array of objects (see + note below). As with all functions returning record, the + caller must explicitly define the structure of the record with + an AS clause. + + select * from json_to_recordset('[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]') as x(a int, b text); + + + a | b +---+----- + 1 | foo + 2 | + + + + + json_strip_nulls(from_json json) + jsonb_strip_nulls(from_json jsonb) + + jsonjsonb + + Returns from_json + with all object fields that have null values omitted. Other null values + are untouched. + + json_strip_nulls('[{"f1":1,"f2":null},2,null,3]') + [{"f1":1},2,null,3] + + + jsonb_set(target jsonb, path text[], new_value jsonb, create_missing boolean) + + jsonb + + Returns target + with the section designated by path + replaced by new_value, or with + new_value added if + create_missing is true ( default is + true) and the item + designated by path does not exist. + As with the path orientated operators, negative integers that + appear in path count from the end + of JSON arrays. + + jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false) + jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}','[2,3,4]') + + [{"f1":[2,3,4],"f2":null},2,null,3] + [{"f1": 1, "f2": null, "f3": [2, 3, 4]}, 2] + + + + + + jsonb_insert(target jsonb, path text[], new_value jsonb, insert_after boolean) + + + jsonb + + Returns target with + new_value inserted. If + target section designated by + path is in a JSONB array, + new_value will be inserted before target or + after if insert_after is true (default is + false). If target section + designated by path is in JSONB object, + new_value will be inserted only if + target does not exist. As with the path + orientated operators, negative integers that appear in + path count from the end of JSON arrays. + + + + jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"') + + + jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true) + + + {"a": [0, "new_value", 1, 2]} + {"a": [0, 1, "new_value", 2]} + + + + jsonb_pretty(from_json jsonb) + + text + + Returns from_json + as indented JSON text. + + jsonb_pretty('[{"f1":1,"f2":null},2,null,3]') + + +[ + { + "f1": 1, + "f2": null + }, + 2, + null, + 3 +] + + + + + +
+ + + + Many of these functions and operators will convert Unicode escapes in + JSON strings to the appropriate single character. This is a non-issue + if the input is type jsonb, because the conversion was already + done; but for json input, this may result in throwing an error, + as noted in . + + + + + + In json_populate_record, json_populate_recordset, + json_to_record and json_to_recordset, + type coercion from the JSON is best effort and may not result + in desired values for some types. JSON keys are matched to + identical column names in the target row type. JSON fields that do not + appear in the target row type will be omitted from the output, and + target columns that do not match any JSON field will simply be NULL. + + + + + + All the items of the path parameter of jsonb_set + as well as jsonb_insert except the last item must be present + in the target. If create_missing is false, all + items of the path parameter of jsonb_set must be + present. If these conditions are not met the target is + returned unchanged. + + + If the last path item is an object key, it will be created if it + is absent and given the new value. If the last path item is an array + index, if it is positive the item to set is found by counting from + the left, and if negative by counting from the right - -1 + designates the rightmost element, and so on. + If the item is out of the range -array_length .. array_length -1, + and create_missing is true, the new value is added at the beginning + of the array if the item is negative, and at the end of the array if + it is positive. + + + + + + The json_typeof function's null return value + should not be confused with a SQL NULL. While + calling json_typeof('null'::json) will + return null, calling json_typeof(NULL::json) + will return a SQL NULL. + + + + + + If the argument to json_strip_nulls contains duplicate + field names in any object, the result could be semantically somewhat + different, depending on the order in which they occur. This is not an + issue for jsonb_strip_nulls since jsonb values never have + duplicate object field names. + + + + + See also for the aggregate + function json_agg which aggregates record + values as JSON, and the aggregate function + json_object_agg which aggregates pairs of values + into a JSON object, and their jsonb equivalents, + jsonb_agg and jsonb_object_agg. + + +
+ +
+ diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b851fe023a..de9d7565e6 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11183,882 +11183,7 @@ table2-mapping - - JSON Functions and Operators - - - JSON - functions and operators - - - - shows the operators that - are available for use with the two JSON data types (see ). - - - - <type>json</type> and <type>jsonb</type> Operators - - - - Operator - Right Operand Type - Description - Example - Example Result - - - - - -> - int - Get JSON array element (indexed from zero, negative - integers count from the end) - '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 - {"c":"baz"} - - - -> - text - Get JSON object field by key - '{"a": {"b":"foo"}}'::json->'a' - {"b":"foo"} - - - ->> - int - Get JSON array element as text - '[1,2,3]'::json->>2 - 3 - - - ->> - text - Get JSON object field as text - '{"a":1,"b":2}'::json->>'b' - 2 - - - #> - text[] - Get JSON object at specified path - '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}' - {"c": "foo"} - - - #>> - text[] - Get JSON object at specified path as text - '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' - 3 - - - -
- - - - There are parallel variants of these operators for both the - json and jsonb types. - The field/element/path extraction operators - return the same type as their left-hand input (either json - or jsonb), except for those specified as - returning text, which coerce the value to text. - The field/element/path extraction operators return NULL, rather than - failing, if the JSON input does not have the right structure to match - the request; for example if no such element exists. The - field/element/path extraction operators that accept integer JSON - array subscripts all support negative subscripting from the end of - arrays. - - - - The standard comparison operators shown in are available for - jsonb, but not for json. They follow the - ordering rules for B-tree operations outlined at . - - - Some further operators also exist only for jsonb, as shown - in . - Many of these operators can be indexed by - jsonb operator classes. For a full description of - jsonb containment and existence semantics, see . - describes how these operators can be used to effectively index - jsonb data. - - - Additional <type>jsonb</type> Operators - - - - Operator - Right Operand Type - Description - Example - - - - - @> - jsonb - Does the left JSON value contain the right JSON - path/value entries at the top level? - '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb - - - <@ - jsonb - Are the left JSON path/value entries contained at the top level within - the right JSON value? - '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb - - - ? - text - Does the string exist as a top-level - key within the JSON value? - '{"a":1, "b":2}'::jsonb ? 'b' - - - ?| - text[] - Do any of these array strings - exist as top-level keys? - '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] - - - ?& - text[] - Do all of these array strings exist - as top-level keys? - '["a", "b"]'::jsonb ?& array['a', 'b'] - - - || - jsonb - Concatenate two jsonb values into a new jsonb value - '["a", "b"]'::jsonb || '["c", "d"]'::jsonb - - - - - text - Delete key/value pair or string - element from left operand. Key/value pairs are matched based - on their key value. - '{"a": "b"}'::jsonb - 'a' - - - - - text[] - Delete multiple key/value pairs or string - elements from left operand. Key/value pairs are matched based - on their key value. - '{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[] - - - - - integer - Delete the array element with specified index (Negative - integers count from the end). Throws an error if top level - container is not an array. - '["a", "b"]'::jsonb - 1 - - - #- - text[] - Delete the field or element with specified path (for - JSON arrays, negative integers count from the end) - '["a", {"b":1}]'::jsonb #- '{1,b}' - - - -
- - - - The || operator concatenates the elements at the top level of - each of its operands. It does not operate recursively. For example, if - both operands are objects with a common key field name, the value of the - field in the result will just be the value from the right hand operand. - - - - - shows the functions that are - available for creating json and jsonb values. - (There are no equivalent functions for jsonb, of the row_to_json - and array_to_json functions. However, the to_jsonb - function supplies much the same functionality as these functions would.) - - - - to_json - - - array_to_json - - - row_to_json - - - json_build_array - - - json_build_object - - - json_object - - - to_jsonb - - - jsonb_build_array - - - jsonb_build_object - - - jsonb_object - - - - JSON Creation Functions - - - - Function - Description - Example - Example Result - - - - - to_json(anyelement) - to_jsonb(anyelement) - - - Returns the value as json or jsonb. - Arrays and composites are converted - (recursively) to arrays and objects; otherwise, if there is a cast - from the type to json, the cast function will be used to - perform the conversion; otherwise, a scalar value is produced. - For any scalar type other than a number, a Boolean, or a null value, - the text representation will be used, in such a fashion that it is a - valid json or jsonb value. - - to_json('Fred said "Hi."'::text) - "Fred said \"Hi.\"" - - - - array_to_json(anyarray [, pretty_bool]) - - - Returns the array as a JSON array. A PostgreSQL multidimensional array - becomes a JSON array of arrays. Line feeds will be added between - dimension-1 elements if pretty_bool is true. - - array_to_json('{{1,5},{99,100}}'::int[]) - [[1,5],[99,100]] - - - - row_to_json(record [, pretty_bool]) - - - Returns the row as a JSON object. Line feeds will be added between - level-1 elements if pretty_bool is true. - - row_to_json(row(1,'foo')) - {"f1":1,"f2":"foo"} - - - json_build_array(VARIADIC "any") - jsonb_build_array(VARIADIC "any") - - - Builds a possibly-heterogeneously-typed JSON array out of a variadic - argument list. - - json_build_array(1,2,'3',4,5) - [1, 2, "3", 4, 5] - - - json_build_object(VARIADIC "any") - jsonb_build_object(VARIADIC "any") - - - Builds a JSON object out of a variadic argument list. By - convention, the argument list consists of alternating - keys and values. - - json_build_object('foo',1,'bar',2) - {"foo": 1, "bar": 2} - - - json_object(text[]) - jsonb_object(text[]) - - - Builds a JSON object out of a text array. The array must have either - exactly one dimension with an even number of members, in which case - they are taken as alternating key/value pairs, or two dimensions - such that each inner array has exactly two elements, which - are taken as a key/value pair. - - json_object('{a, 1, b, "def", c, 3.5}') - json_object('{{a, 1},{b, "def"},{c, 3.5}}') - {"a": "1", "b": "def", "c": "3.5"} - - - json_object(keys text[], values text[]) - jsonb_object(keys text[], values text[]) - - - This form of json_object takes keys and values pairwise from two separate - arrays. In all other respects it is identical to the one-argument form. - - json_object('{a, b}', '{1,2}') - {"a": "1", "b": "2"} - - - -
- - - - array_to_json and row_to_json have the same - behavior as to_json except for offering a pretty-printing - option. The behavior described for to_json likewise applies - to each individual value converted by the other JSON creation functions. - - - - - - The extension has a cast - from hstore to json, so that - hstore values converted via the JSON creation functions - will be represented as JSON objects, not as primitive string values. - - - - - shows the functions that - are available for processing json and jsonb values. - - - - json_array_length - - - jsonb_array_length - - - json_each - - - jsonb_each - - - json_each_text - - - jsonb_each_text - - - json_extract_path - - - jsonb_extract_path - - - json_extract_path_text - - - jsonb_extract_path_text - - - json_object_keys - - - jsonb_object_keys - - - json_populate_record - - - jsonb_populate_record - - - json_populate_recordset - - - jsonb_populate_recordset - - - json_array_elements - - - jsonb_array_elements - - - json_array_elements_text - - - jsonb_array_elements_text - - - json_typeof - - - jsonb_typeof - - - json_to_record - - - jsonb_to_record - - - json_to_recordset - - - jsonb_to_recordset - - - json_strip_nulls - - - jsonb_strip_nulls - - - jsonb_set - - - jsonb_insert - - - jsonb_pretty - - - - JSON Processing Functions - - - - Function - Return Type - Description - Example - Example Result - - - - - json_array_length(json) - jsonb_array_length(jsonb) - - int - - Returns the number of elements in the outermost JSON array. - - json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') - 5 - - - json_each(json) - jsonb_each(jsonb) - - setof key text, value json - setof key text, value jsonb - - - Expands the outermost JSON object into a set of key/value pairs. - - select * from json_each('{"a":"foo", "b":"bar"}') - - - key | value ------+------- - a | "foo" - b | "bar" - - - - - json_each_text(json) - jsonb_each_text(jsonb) - - setof key text, value text - - Expands the outermost JSON object into a set of key/value pairs. The - returned values will be of type text. - - select * from json_each_text('{"a":"foo", "b":"bar"}') - - - key | value ------+------- - a | foo - b | bar - - - - - json_extract_path(from_json json, VARIADIC path_elems text[]) - jsonb_extract_path(from_json jsonb, VARIADIC path_elems text[]) - - jsonjsonb - - - Returns JSON value pointed to by path_elems - (equivalent to #> operator). - - json_extract_path('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4') - {"f5":99,"f6":"foo"} - - - json_extract_path_text(from_json json, VARIADIC path_elems text[]) - jsonb_extract_path_text(from_json jsonb, VARIADIC path_elems text[]) - - text - - Returns JSON value pointed to by path_elems - as text - (equivalent to #>> operator). - - json_extract_path_text('{"f2":{"f3":1},"f4":{"f5":99,"f6":"foo"}}','f4', 'f6') - foo - - - json_object_keys(json) - jsonb_object_keys(jsonb) - - setof text - - Returns set of keys in the outermost JSON object. - - json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}') - - - json_object_keys ------------------- - f1 - f2 - - - - - json_populate_record(base anyelement, from_json json) - jsonb_populate_record(base anyelement, from_json jsonb) - - anyelement - - Expands the object in from_json to a row - whose columns match the record type defined by base - (see note below). - - select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c": {"d": 4, "e": "a b c"}}') - - - a | b | c ----+-----------+------------- - 1 | {2,"a b"} | (4,"a b c") - - - - - json_populate_recordset(base anyelement, from_json json) - jsonb_populate_recordset(base anyelement, from_json jsonb) - - setof anyelement - - Expands the outermost array of objects - in from_json to a set of rows whose - columns match the record type defined by base (see - note below). - - select * from json_populate_recordset(null::myrowtype, '[{"a":1,"b":2},{"a":3,"b":4}]') - - - a | b ----+--- - 1 | 2 - 3 | 4 - - - - - json_array_elements(json) - jsonb_array_elements(jsonb) - - setof json - setof jsonb - - - Expands a JSON array to a set of JSON values. - - select * from json_array_elements('[1,true, [2,false]]') - - - value ------------ - 1 - true - [2,false] - - - - - json_array_elements_text(json) - jsonb_array_elements_text(jsonb) - - setof text - - Expands a JSON array to a set of text values. - - select * from json_array_elements_text('["foo", "bar"]') - - - value ------------ - foo - bar - - - - - json_typeof(json) - jsonb_typeof(jsonb) - - text - - Returns the type of the outermost JSON value as a text string. - Possible types are - object, array, string, number, - boolean, and null. - - json_typeof('-123.4') - number - - - json_to_record(json) - jsonb_to_record(jsonb) - - record - - Builds an arbitrary record from a JSON object (see note below). As - with all functions returning record, the caller must - explicitly define the structure of the record with an AS - clause. - - select * from json_to_record('{"a":1,"b":[1,2,3],"c":[1,2,3],"e":"bar","r": {"a": 123, "b": "a b c"}}') as x(a int, b text, c int[], d text, r myrowtype) - - - a | b | c | d | r ----+---------+---------+---+--------------- - 1 | [1,2,3] | {1,2,3} | | (123,"a b c") - - - - - json_to_recordset(json) - jsonb_to_recordset(jsonb) - - setof record - - Builds an arbitrary set of records from a JSON array of objects (see - note below). As with all functions returning record, the - caller must explicitly define the structure of the record with - an AS clause. - - select * from json_to_recordset('[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]') as x(a int, b text); - - - a | b ----+----- - 1 | foo - 2 | - - - - - json_strip_nulls(from_json json) - jsonb_strip_nulls(from_json jsonb) - - jsonjsonb - - Returns from_json - with all object fields that have null values omitted. Other null values - are untouched. - - json_strip_nulls('[{"f1":1,"f2":null},2,null,3]') - [{"f1":1},2,null,3] - - - jsonb_set(target jsonb, path text[], new_value jsonb, create_missing boolean) - - jsonb - - Returns target - with the section designated by path - replaced by new_value, or with - new_value added if - create_missing is true ( default is - true) and the item - designated by path does not exist. - As with the path orientated operators, negative integers that - appear in path count from the end - of JSON arrays. - - jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false) - jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}','[2,3,4]') - - [{"f1":[2,3,4],"f2":null},2,null,3] - [{"f1": 1, "f2": null, "f3": [2, 3, 4]}, 2] - - - - - - jsonb_insert(target jsonb, path text[], new_value jsonb, insert_after boolean) - - - jsonb - - Returns target with - new_value inserted. If - target section designated by - path is in a JSONB array, - new_value will be inserted before target or - after if insert_after is true (default is - false). If target section - designated by path is in JSONB object, - new_value will be inserted only if - target does not exist. As with the path - orientated operators, negative integers that appear in - path count from the end of JSON arrays. - - - - jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"') - - - jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true) - - - {"a": [0, "new_value", 1, 2]} - {"a": [0, 1, "new_value", 2]} - - - - jsonb_pretty(from_json jsonb) - - text - - Returns from_json - as indented JSON text. - - jsonb_pretty('[{"f1":1,"f2":null},2,null,3]') - - -[ - { - "f1": 1, - "f2": null - }, - 2, - null, - 3 -] - - - - - -
- - - - Many of these functions and operators will convert Unicode escapes in - JSON strings to the appropriate single character. This is a non-issue - if the input is type jsonb, because the conversion was already - done; but for json input, this may result in throwing an error, - as noted in . - - - - - - In json_populate_record, json_populate_recordset, - json_to_record and json_to_recordset, - type coercion from the JSON is best effort and may not result - in desired values for some types. JSON keys are matched to - identical column names in the target row type. JSON fields that do not - appear in the target row type will be omitted from the output, and - target columns that do not match any JSON field will simply be NULL. - - - - - - All the items of the path parameter of jsonb_set - as well as jsonb_insert except the last item must be present - in the target. If create_missing is false, all - items of the path parameter of jsonb_set must be - present. If these conditions are not met the target is - returned unchanged. - - - If the last path item is an object key, it will be created if it - is absent and given the new value. If the last path item is an array - index, if it is positive the item to set is found by counting from - the left, and if negative by counting from the right - -1 - designates the rightmost element, and so on. - If the item is out of the range -array_length .. array_length -1, - and create_missing is true, the new value is added at the beginning - of the array if the item is negative, and at the end of the array if - it is positive. - - - - - - The json_typeof function's null return value - should not be confused with a SQL NULL. While - calling json_typeof('null'::json) will - return null, calling json_typeof(NULL::json) - will return a SQL NULL. - - - - - - If the argument to json_strip_nulls contains duplicate - field names in any object, the result could be semantically somewhat - different, depending on the order in which they occur. This is not an - issue for jsonb_strip_nulls since jsonb values never have - duplicate object field names. - - - - - See also for the aggregate - function json_agg which aggregates record - values as JSON, and the aggregate function - json_object_agg which aggregates pairs of values - into a JSON object, and their jsonb equivalents, - jsonb_agg and jsonb_object_agg. - - -
+ &func-sqljson; Sequence Manipulation Functions diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index cc7cd1ed2c..8c51e4ef58 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -102,6 +102,8 @@ ?& ?| @> + @? + @~
@@ -109,6 +111,8 @@ jsonb @> + @? + @~ diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml index e7b68fa0d2..9c896c590a 100644 --- a/doc/src/sgml/json.sgml +++ b/doc/src/sgml/json.sgml @@ -22,8 +22,16 @@ - There are two JSON data types: json and jsonb. - They accept almost identical sets of values as + PostgreSQL offers two types for storing JSON + data: json and jsonb. To implement effective query + mechanisms for these data types, PostgreSQL + also provides the jsonpath data type described in + . + + + + The json and jsonb data types + accept almost identical sets of values as input. The major practical difference is one of efficiency. The json data type stores an exact copy of the input text, which processing functions must reparse on each execution; while @@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb; in this example, even though those are semantically insignificant for purposes such as equality checks. + + + For the list of built-in functions and operators available for + constructing and processing JSON values, see . + @@ -535,6 +548,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu therefore ill-suited for applications that often perform such searches. + + jsonb_ops and jsonb_path_ops also + support queries with jsonpath operators @? + and @~. The previous example for @> + operator can be rewritten as follows: + +-- Find documents in which the key "tags" contains array element "qui" +SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")'; +SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"'; + + + + jsonb also supports btree and hash indexes. These are usually useful only if it's important to check @@ -593,4 +619,185 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu lists, and scalars, as appropriate. + + + jsonpath Type + + + jsonpath + + + + The jsonpath type implements support for the SQL/JSON path language + in PostgreSQL to effectively query JSON data. + It provides a binary representation of the parsed SQL/JSON path + expression that specifies the items to be retrieved by the path + engine from the JSON data for further processing with the + SQL/JSON query functions. + + + + The SQL/JSON path language is fully integrated into the SQL engine: + the semantics of its predicates and operators generally follow SQL. + At the same time, to provide a most natural way of working with JSON data, + SQL/JSON path syntax uses some of the JavaScript conventions: + + + + + + Dot . is used for member access. + + + + + Square brackets [] are used for array access. + + + + + SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1. + + + + + + An SQL/JSON path expression is an SQL character string literal, + so it must be enclosed in single quotes. Following the JavaScript + conventions, character string literals within the path expression + must be enclosed in double quotes. Any single quotes within this + character string literal must be escaped with a single quote + by the SQL convention. + + + + A path expression consists of a sequence of path elements, + which can be the following: + + + + Path literals of JSON primitive types: + Unicode text, numeric, true, false, or null. + + + + + Path variables listed in . + + + + + Accessor operators listed in . + + + + + jsonpath operators + and methods listed in + + + + + Parentheses, which can be used to provide filter expressions + or define the order of path evaluation. + + + + + + + For details on using jsonpath expressions with SQL/JSON + query functions, see . + + + + <type>jsonpath</type> Variables + + + + Variable + Description + + + + + $ + A variable representing the JSON text to be queried + (the context item). + + + + $varname + A named variable. Its value must be set in the + PASSING clause. See + for details. + + + + @ + A variable representing the result of path evaluation + in filter expressions. + + + + +
+ + + <type>jsonpath</type> Accessors + + + + Accessor Operator + Description + + + + + .key + .$"varname" + + Member accessor that returns an object + member with the specified key. If the key name is a named + variable starting with $ or does not + meet the JavaScript rules of an identifier, it must be enclosed in + double quotes as a character string literal. + + + .* + Wildcard member accessor that returns the values of all + members located at the top level of the current object. + + + .** + Recursive wildcard member accessor that processes + all levels of the JSON hierarchy of the current object and + returns all the member values, regardless of their nesting + level. This is a PostgreSQL + extension of the SQL/JSON standard. + + + + [subscript, ...] + [subscript to last] + + Array element accessor. The provided numeric subscripts return + the corresponding array elements. The first element in an array is + accessed with [0]. The last keyword denotes the last subscript + in an array and can be used to handle arrays of unknown length. + + + [*] + Wildcard array element accessor that returns all array elements. + + + +
+ + + For details on using jsonpath expressions with SQL/JSON query + functions, see . + + +
From 1388737a00401a562ca44cd93064047ff32fd800 Mon Sep 17 00:00:00 2001 From: Liudmila Mantrova Date: Wed, 20 Jun 2018 11:22:05 +0300 Subject: [PATCH 96/97] Improved JSON_TABLE description based on Nikita Glukhov's input --- doc/src/sgml/func-sqljson.sgml | 166 ++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 64 deletions(-) diff --git a/doc/src/sgml/func-sqljson.sgml b/doc/src/sgml/func-sqljson.sgml index e5693081a9..df9e24d0f6 100644 --- a/doc/src/sgml/func-sqljson.sgml +++ b/doc/src/sgml/func-sqljson.sgml @@ -902,8 +902,8 @@ INSERT INTO my_films VALUES ( "director" : "Alfred Hitchcock" } ] }, { "kind" : "thriller", "films" : [ { "title" : "Vertigo", - "director" : "Hitchcock" } ] }, - { "films" : [ + "director" : "Alfred Hitchcock" } ] }, + { "kind" : "drama", "films" : [ { "title" : "Yojimbo", "director" : "Akira Kurosawa" } ] } ] }'); @@ -1339,7 +1339,7 @@ SELECT FROM my_films; json_query ------------ - ["comedy", "horror", "thriller"] + ["comedy", "horror", "thriller", "drama"] (1 row) @@ -1402,7 +1402,7 @@ SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES); JSON_TABLE ( - json_api_common_syntax [ AS json_path_name ] + json_api_common_syntax [ AS path_name ] COLUMNS ( json_table_column [, ...] ) [ PLAN ( json_table_plan ) | PLAN DEFAULT ( { INNER | OUTER } [ , { CROSS | UNION } ] @@ -1412,30 +1412,29 @@ JSON_TABLE ( where json_table_column is: - { name type [ PATH json_path_specification ] - [ { ERROR | NULL | DEFAULT expression } ON EMPTY ] - [ { ERROR | NULL | DEFAULT expression } ON ERROR ] - | name type FORMAT json_representation - [ PATH json_path_specification ] - [ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } - [ ARRAY ] WRAPPER ] - [ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ] - [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON EMPTY ] - [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON ERROR ] - | NESTED PATH json_path_specification [ AS path_name ] - COLUMNS ( json_table_column [, ...] ) - | name FOR ORDINALITY } + name type [ PATH json_path_specification ] + [ { ERROR | NULL | DEFAULT expression } ON EMPTY ] + [ { ERROR | NULL | DEFAULT expression } ON ERROR ] + | name type FORMAT json_representation + [ PATH json_path_specification ] + [ { WITHOUT | WITH { CONDITIONAL | [UNCONDITIONAL] } } + [ ARRAY ] WRAPPER ] + [ { KEEP | OMIT } QUOTES [ ON SCALAR STRING ] ] + [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON EMPTY ] + [ { ERROR | NULL | EMPTY { ARRAY | OBJECT } } ON ERROR ] + | NESTED PATH json_path_specification [ AS path_name ] + COLUMNS ( json_table_column [, ...] ) + | name FOR ORDINALITY json_table_plan is: - path_name { OUTER | INNER | CROSS | UNION } - { path_name | ( nested_plan ) } + path_name [ { OUTER | INNER } json_table_plan_primary ] + | json_table_plan_primary { UNION json_table_plan_primary } [...] + | json_table_plan_primary { CROSS json_table_plan_primary } [...] -nested_plan is: +json_table_plan_primary is: - path_name { OUTER | INNER } { path_name | ( nested_plan ) } - | { path_name | ( nested_plan ) } - { { UNION | CROSS } { path_name | ( nested_plan ) } }[...] + path_name | ( json_table_plan ) @@ -1453,9 +1452,9 @@ where json_table_column is: Taking JSON data as input, JSON_TABLE uses - a path expression to extract a part of the provided input that + a path expression to extract a part of the provided data that will be used as a row pattern for the - contructed view. Each SQL/JSON item at the top level of the row pattern serves + constructed view. Each SQL/JSON item at the top level of the row pattern serves as the source for a separate row in the constructed relational view. @@ -1467,15 +1466,19 @@ where json_table_column is: the row pattern, extracts a JSON item, and returns it as a separate SQL value for the specified column. If the required value is stored in a nested level of the row pattern, it can be extracted - using the NESTED PATH subclause. + using the NESTED PATH subclause. Joining the + columns returned by NESTED PATH can add multiple + new rows to the constructed view. Such rows are called + child rows, as opposed to the parent row + that generates them. The rows produced by JSON_TABLE are laterally joined to the row that generated them, so you do not have to explicitly join - the constructed view with the original table. You can specify - how to handle empty rows returned from the nested levels of - the row pattern using the PLAN clause. + the constructed view with the original table holding JSON + data. Optionally, you can specify how to join the columns returned + by NESTED PATH using the PLAN clause. @@ -1502,18 +1505,16 @@ where json_table_column is: - COLUMNS( { json_table_column } [, ...] ) + COLUMNS( { json_table_column } [, ...] ) The COLUMNS clause defining the schema of the - constructed table. In this clause, you must specify all the columns - to be filled with SQL/JSON items returned by JSON_TABLE. - You must provide the name and type for each column. Only scalar - types are supported. + constructed view. In this clause, you must specify all the columns + to be filled with SQL/JSON items. Only scalar column types are supported. The json_table_column - expression can use one of the following syntax options: + expression has the following syntax variants: @@ -1601,14 +1602,13 @@ where json_table_column is: so you can go down multiple nested levels by specifying several NESTED PATH subclauses within each other. It allows to unnest the hierarchy of JSON objects and arrays - in a single function invocation rather than chaining several JSON_TABLE - expressions in an SQL statement. + in a single function invocation rather than chaining several + JSON_TABLE expressions in an SQL statement. You can use the PLAN clause to define how - to handle empty rows when joining the columns returned by - NESTED PATH clauses. + to join the columns returned by NESTED PATH clauses. @@ -1622,7 +1622,8 @@ where json_table_column is: Adds an ordinality column that provides sequential row numbering. You can have only one ordinality column per table. Row numbering - is 1-based. + is 1-based. For child rows that result from the NESTED PATH + clauses, the parent row number is repeated. @@ -1641,28 +1642,31 @@ where json_table_column is: The optional json_path_name serves as an identifier of the provided json_path_specification. The path name must be unique and cannot coincide with column names. - You must specify the path name when using the PLAN - clause. + When using the PLAN clause, you must specify the names + for all the paths, including the row pattern. Each path name can appear in + the PLAN clause only once. - PLAN json_table_plan + PLAN ( json_table_plan ) - Defines how to handle empty rows when joining the columns - returned by NESTED PATH clauses. + Defines how to join the data returned by NESTED PATH + clauses to the constructed view. - In relation to the columns returned directly from the row expression - or by the NESTED PATH clause of a higher level, nested columns - are considered to be child columns. - Each NESTED PATH clause can include - several columns, which are called sibling columns. + Each NESTED PATH clause can generate one or more + columns, which are considered to be siblings + to each other. In relation to the columns returned directly from the row + expression or by the NESTED PATH clause of a + higher level, these columns are child columns. + Sibling columns are always joined first. Once they are processed, + the resulting rows are joined to the parent row. To join columns with parent/child relationship, you can use: @@ -1675,9 +1679,9 @@ where json_table_column is: - Use INNER JOIN, so that the output includes - only those rows that have the corresponding values in both - columns. + Use INNER JOIN, so that the parent row + is omitted from the output if it does not have any child rows + after joining the data returned by NESTED PATH. @@ -1689,10 +1693,11 @@ where json_table_column is: - Use LEFT OUTER JOIN, so that all rows of the - left-hand column must be included into the output at least once, while the rows of the right-hand - column are only included if they have a match in the left column. If the corresponding - value is missing from the right column, the NULL value is inserted into the right column. + Use LEFT OUTER JOIN, so that the parent row + is always included into the output even if it does not have any child rows + after joining the data returned by NESTED PATH, with NULL values + inserted into the child columns if the corresponding + values are missing. This is the default option for joining columns with parent/child relationship. @@ -1713,10 +1718,9 @@ where json_table_column is: - Use FULL OUTER JOIN, so that all rows of the - left-hand and the right-hand columns are included into the output, with - NULL values inserted into either of the columns if the corresponding value - is missing. + Use FULL OUTER JOIN ON FALSE, so that both parent and child + rows are included into the output, with NULL values inserted + into both child and parrent columns for all missing values. This is the default option for joining sibling columns. @@ -1745,11 +1749,17 @@ where json_table_column is: - PLAN DEFAULT + PLAN DEFAULT ( option [, ... ] ) - Redefines the default behavior for all columns at once. + Overrides the default joining plans. The INNER and + OUTER options define the joining plan for parent/child + columns, while UNION and CROSS + affect the sibling columns. You can override the default plans for all columns at once. + Even though the path names are not incuded into the PLAN DEFAULT + clause, they must be provided for all the paths to conform to + the SQL/JSON standard. @@ -1779,8 +1789,36 @@ SELECT jt.* FROM 1 | comedy | The Dinner Game | Francis Veber 2 | horror | Psycho | Alfred Hitchcock 3 | thriller | Vertigo | Hitchcock - 4 | (null) | Yojimbo | Akira Kurosawa + 4 | drama | Yojimbo | Akira Kurosawa (5 rows) + + + + + Find a director that has done films in two different genres: + +SELECT + director1 AS director, title1, kind1, title2, kind2 +FROM + my_films, + JSON_TABLE ( js, '$.favorites' AS favs COLUMNS ( + NESTED PATH '$[*]' AS films1 COLUMNS ( + kind1 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film1 COLUMNS ( + title1 text PATH '$.title', + director1 text PATH '$.director') + ), + NESTED PATH '$[*]' AS films2 COLUMNS ( + kind2 text PATH '$.kind', + NESTED PATH '$.films[*]' AS film2 COLUMNS ( + title2 text PATH '$.title', + director2 text PATH '$.director' + ) + ) + ) + PLAN (favs OUTER ((films1 INNER film1) CROSS (films2 INNER film2))) + ) AS jt + WHERE kind1 > kind2 AND director1 = director2; From 7618ec5a6ba96876adf8f98d7223708ccc0eb52e Mon Sep 17 00:00:00 2001 From: Liudmila Mantrova Date: Thu, 16 Aug 2018 19:22:40 +0300 Subject: [PATCH 97/97] DOC: improved descriptions of handling data types by SQL/JSON functions based on feedback from Nikita Glukhov --- doc/src/sgml/biblio.sgml | 2 +- doc/src/sgml/func-sqljson.sgml | 174 +++++++++++++++++++++++++++------ 2 files changed, 143 insertions(+), 33 deletions(-) diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml index a7771dc139..f06305d9dc 100644 --- a/doc/src/sgml/biblio.sgml +++ b/doc/src/sgml/biblio.sgml @@ -136,7 +136,7 @@ 1988 - + SQL Technical Report Part 6: SQL support for JavaScript Object Notation (JSON) diff --git a/doc/src/sgml/func-sqljson.sgml b/doc/src/sgml/func-sqljson.sgml index df9e24d0f6..af5fbe660b 100644 --- a/doc/src/sgml/func-sqljson.sgml +++ b/doc/src/sgml/func-sqljson.sgml @@ -24,8 +24,9 @@ - To learn more about the SQL/JSON standard, see . - For details on JSON types supported in PostgreSQL, see . + To learn more about the SQL/JSON standard, see . + For details on JSON types supported in PostgreSQL, + see . @@ -1064,15 +1065,13 @@ SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]'); - The output clause that specifies the data type of the returned value. - Out of the box, PostgreSQL - supports the following types: json, jsonb, - bytea, and character string types (text, char, - varchar, and nchar). - The extracted value must be a single SQL/JSON scalar item - and have a cast to the specified type. Otherwise, an error occurs. + The output clause that specifies the SQL data type the extracted value + will be represented as. The extracted value must be a single + SQL/JSON scalar item and have a cast to the + specified type. Otherwise, an error occurs. By default, JSON_VALUE returns a string - of the text type. + of the text type. For details, see + . @@ -1112,11 +1111,10 @@ SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]'); Examples - Extract an SQL/JSON value and return it as an SQL - scalar of the specified type. Note that - JSON_VALUE can only return a - single scalar, and the returned value must have a - cast to the specified return type: + Extract an SQL/JSON value and return it as an SQL scalar + of the specified type. Note that JSON_VALUE + can only return a single scalar, and the extracted value must have + a cast to the specified return type: @@ -1226,7 +1224,12 @@ SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 1 ON ERROR); - The output clause that specifies the data type of the returned value. + The output clause that specifies the SQL data type the extracted + SQL/JSON object or array will be represented as. By default, + JSON_QUERY returns a string of the + json type, unless the context item is explicitly + converted to another type. In this case, the return type corresponds + to the type of the context item. For details, see . @@ -2032,19 +2035,127 @@ FROM The output clause that specifies the return type of the generated - JSON object. Out of the box, PostgreSQL - supports the following types: json, jsonb, - bytea, and character string types (text, char, - varchar, and nchar). - To use other types, you must create the CAST from json for this type. - By default, the json type is returned. + SQL/JSON item. + - The optional FORMAT clause is provided to conform to the SQL/JSON standard. - + The RETURNING clause is common for both constructor + and query SQL/JSON functions. All functions rely on type casts + available in PostgreSQL, but have some + implementation specifics that determines which types can be used + by a particular function. Type casts supported by + PostgreSQL out of the box are listed + in . + + + + Constructor functions support json, jsonb, + bytea, and any other types that have a cast from + the SQL type of the constructed item. By default, + the constructed item is represented as json. + + - The output clause is common for both constructor and query SQL/JSON functions. + For the JSON_VALUE function, you can use + json, jsonb, or any other type that has + a cast from the SQL type that corresponds to the SQL/JSON type of the + extracted value. The output to json and jsonb + types will always work regardless of the type of the extracted value, + since SQL/JSON items are converted directly to json or + jsonb without casting. With other types, the result + depends on the combination of the type of the extracted value + and the specified return type: if no cast is found, an error occurs. + By default, the SQL/JSON item returned by + JSON_VALUE is represented as text. + + + With the JSON_QUERY function, you can use any + return types. The implementation details are as follows: + + + + For SQL/JSON items of a scalar SQL type, a cast from json + or jsonb is searched, depending on the type of the context + item. If no cast is found, an input/output function is used for + conversion. As an exception, if you use the bytea return + type, the extracted SQL/JSON item is converted to the return type as + follows: convert_to(json[b]::text, 'UTF8'). + If JSON_QUERY extracts an SQL/JSON string, + which is not wrapped using the WRAPPER clause, + and the OMIT QUOTES clause is specified, the string + contents is converted to the return type using the input/output + function that corresponds to this return type. + + + + + For SQL/JSON items of non-scalar SQL types (arrays, records, and + domains), the json[b]_populate_record() function + is used for conversion to the specified return type. This is a + PostgreSQL extension of the SQL/JSON standard. + + + + By default, JSON_QUERY returns a string of the + json type, unless the context item is is explicitly + converted to another type. In this case, the return type corresponds + to the type of the context item. + + + + Type Casts Supported by <productname>PostgreSQL</productname> + + + + SQL/JSON type + SQL Type + + + + + string + text + + + number + numeric + + + boolean + boolean + + + date + date + + + time + time + + + time with tz + timetz + + + timestamp + timestamp + + + timestamp with tz + timestamptz + + + +
+ + + The optional FORMAT clause is provided + to conform to the SQL/JSON standard and can only take the + json value as an argument. + PostgreSQL currently supports + only the UTF8 encoding. +
@@ -2067,10 +2178,10 @@ FROM The SQL/JSON query functions pass the provided path expression to the path engine for evaluation. - The path expression is evaluated from left to right. - You can use parentheses to change the order of operations. - If the evaluation is successful, an SQL/JSON sequence is produced. - The evaluation result is returned to the SQL/JSON query function + The path expression is evaluated from left to right, but + you can use parentheses to change the order of operations. + If the evaluation is successful, an SQL/JSON sequence is produced, + and the evaluation result is returned to the SQL/JSON query function that completes the specified computation. If the query result must be JSON text, you have to use the WITH WRAPPER clause or enclose the path expression in square brackets to ensure @@ -2078,7 +2189,7 @@ FROM - A typical path expression has the following structure: + A path expression has the following structure: @@ -2508,7 +2619,7 @@ FROM
like_regex - Tests pattern matching with regular expressions + Tests pattern matching with POSIX regular expressions ["abc", "abd", "aBdC", "abdacb", "babc"] $[*] ? (@ like_regex "^ab.*c" flag "i") "abc", "aBdC", "abdacb" @@ -3545,4 +3656,3 @@ FROM -