From 3427a44c3970dc3942a1d32a699b2a52e18628a7 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 19:38:32 +0300 Subject: [PATCH 01/75] 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 2923afe7b6..cec2a3955f 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -977,7 +977,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw, static void DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid); -static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out); +static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out, + bool strict); #ifdef DEBUG_TO_FROM_CHAR static void dump_index(const KeyWord *k, const int *index); @@ -994,7 +995,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); @@ -3007,13 +3008,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; @@ -3374,6 +3377,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out) } } } + + if (strict) + { + if (n->type != NODE_TYPE_END) + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("input string is too short for datetime format"))); + + while (*s == ' ') + s++; + + if (*s != '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_DATETIME_FORMAT), + errmsg("trailing characters remain in input string after " + "datetime format"))); + } } /* @@ -3683,7 +3703,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) @@ -3718,7 +3738,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)) @@ -3751,9 +3771,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; @@ -3807,7 +3829,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 266b66d17ade67844e40ddc8904a282e158207e9 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 23:26:42 +0300 Subject: [PATCH 02/75] 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 cec2a3955f..bdf22abe3b 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -995,8 +995,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); @@ -3703,7 +3703,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) @@ -3738,7 +3739,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)) @@ -3775,12 +3777,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; @@ -3791,15 +3793,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) { /* @@ -3831,11 +3833,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 1c417862e4fa8d661e6bea16dfb996ad865a52be Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 10 Jan 2018 23:31:01 +0300 Subject: [PATCH 03/75] 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 cb6b5e55bf..7d5c8ac6c8 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -40,11 +40,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) @@ -1210,7 +1205,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) @@ -1376,7 +1371,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] = { @@ -1954,7 +1949,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 bdf22abe3b..d7f9025d14 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -86,6 +86,7 @@ #endif #include "catalog/pg_collation.h" +#include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" #include "utils/date.h" @@ -962,6 +963,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 @@ -996,7 +1001,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); @@ -3414,6 +3419,103 @@ DCH_prevent_counter_overflow(void) } } +/* 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) @@ -3704,7 +3806,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) @@ -3740,7 +3842,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)) @@ -3761,6 +3863,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 * @@ -3773,12 +4024,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; @@ -3833,6 +4088,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 449164ae7e..fcc6d23e64 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 de9e9ade5c..165f0e7965 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 e1cf97cef3e32990f3d28d53a265a9dc0c6e63b5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 6 Nov 2018 15:05:24 +0300 Subject: [PATCH 04/75] Implement FF1-FF9 datetime formatting fields --- src/backend/utils/adt/formatting.c | 136 ++++++++++++++++++---- src/test/regress/expected/horology.out | 86 ++++++++++++++ src/test/regress/expected/timestamp.out | 23 ++++ src/test/regress/expected/timestamptz.out | 22 ++++ src/test/regress/sql/horology.sql | 14 +++ src/test/regress/sql/timestamp.sql | 13 +++ src/test/regress/sql/timestamptz.sql | 13 +++ 7 files changed, 286 insertions(+), 21 deletions(-) diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index d7f9025d14..f7d4a504c9 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -437,7 +437,8 @@ typedef struct clock, /* 12 or 24 hour clock? */ tzsign, /* +1, -1 or 0 if timezone info is absent */ tzh, - tzm; + tzm, + ff; /* fractional precision */ } TmFromChar; #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar)) @@ -597,6 +598,15 @@ typedef enum DCH_Day, DCH_Dy, DCH_D, + DCH_FF1, + DCH_FF2, + DCH_FF3, + DCH_FF4, + DCH_FF5, + DCH_FF6, + DCH_FF7, + DCH_FF8, + DCH_FF9, DCH_FX, /* global suffix */ DCH_HH24, DCH_HH12, @@ -646,6 +656,15 @@ typedef enum DCH_dd, DCH_dy, DCH_d, + DCH_ff1, + DCH_ff2, + DCH_ff3, + DCH_ff4, + DCH_ff5, + DCH_ff6, + DCH_ff7, + DCH_ff8, + DCH_ff9, DCH_fx, DCH_hh24, DCH_hh12, @@ -746,7 +765,16 @@ static const KeyWord DCH_keywords[] = { {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE}, {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE}, {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, - {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */ + {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */ + {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"FF7", 3, DCH_FF7, false, FROM_CHAR_DATE_NONE}, + {"FF8", 3, DCH_FF8, false, FROM_CHAR_DATE_NONE}, + {"FF9", 3, DCH_FF9, false, FROM_CHAR_DATE_NONE}, + {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */ {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, @@ -795,7 +823,16 @@ static const KeyWord DCH_keywords[] = { {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN}, {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE}, {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN}, - {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */ + {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */ + {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE}, + {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE}, + {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE}, + {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE}, + {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE}, + {"ff7", 3, DCH_FF7, false, FROM_CHAR_DATE_NONE}, + {"ff8", 3, DCH_FF8, false, FROM_CHAR_DATE_NONE}, + {"ff9", 3, DCH_FF9, false, FROM_CHAR_DATE_NONE}, + {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */ {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE}, {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE}, @@ -896,10 +933,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1, - DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF, + DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF, DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY, -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc, - DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, + DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww, -1, DCH_y_yyy, -1, -1, -1, -1 @@ -1001,7 +1038,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, int *flags); + bool strict, struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags); static char *fill_str(char *str, int c, int max); static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); static char *int_to_roman(int number); @@ -2520,17 +2557,39 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col str_numth(s, s, S_TH_TYPE(n->suffix)); s += strlen(s); break; - case DCH_MS: /* millisecond */ - sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000))); - if (S_THth(n->suffix)) - str_numth(s, s, S_TH_TYPE(n->suffix)); +#define DCH_to_char_fsec(frac_fmt, frac_val) \ + sprintf(s, frac_fmt, (int) (frac_val)); \ + if (S_THth(n->suffix)) \ + str_numth(s, s, S_TH_TYPE(n->suffix)); \ s += strlen(s); + case DCH_FF1: /* decisecond */ + DCH_to_char_fsec("%01d", in->fsec / INT64CONST(100000)); + break; + case DCH_FF2: /* centisecond */ + DCH_to_char_fsec("%02d", in->fsec / INT64CONST(10000)); + break; + case DCH_FF3: + case DCH_MS: /* millisecond */ + DCH_to_char_fsec("%03d", in->fsec / INT64CONST(1000)); + break; + case DCH_FF4: + DCH_to_char_fsec("%04d", in->fsec / INT64CONST(100)); + break; + case DCH_FF5: + DCH_to_char_fsec("%05d", in->fsec / INT64CONST(10)); break; + case DCH_FF6: case DCH_US: /* microsecond */ - sprintf(s, "%06d", (int) in->fsec); - if (S_THth(n->suffix)) - str_numth(s, s, S_TH_TYPE(n->suffix)); - s += strlen(s); + DCH_to_char_fsec("%06d", in->fsec); + break; +#undef DCH_to_char_fsec + case DCH_FF7: + case DCH_FF8: + case DCH_FF9: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("datetime formatting field \"%s\" is not supported", + n->key->name))); break; case DCH_SSSS: sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR + @@ -3157,8 +3216,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) SKIP_THth(s, n->suffix); break; + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + out->ff = n->key->id - DCH_FF1 + 1; + /* fall through */ case DCH_US: /* microsecond */ - len = from_char_parse_int_len(&out->us, &s, 6, n); + len = from_char_parse_int_len(&out->us, &s, + n->key->id == DCH_US ? 6 : + out->ff, n); out->us *= len == 1 ? 100000 : len == 2 ? 10000 : @@ -3172,6 +3241,14 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict) from_char_parse_int(&out->ssss, &s, n); SKIP_THth(s, n->suffix); break; + case DCH_FF7: + case DCH_FF8: + case DCH_FF9: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("datetime formatting field \"%s\" is not supported", + n->key->name))); + break; case DCH_tz: case DCH_TZ: case DCH_OF: @@ -3450,6 +3527,15 @@ DCH_datetime_type(FormatNode *node) case DCH_SS: case DCH_MS: /* millisecond */ case DCH_US: /* microsecond */ + case DCH_FF1: + case DCH_FF2: + case DCH_FF3: + case DCH_FF4: + case DCH_FF5: + case DCH_FF6: + case DCH_FF7: + case DCH_FF8: + case DCH_FF9: case DCH_SSSS: flags |= DCH_TIMED; break; @@ -3804,9 +3890,10 @@ to_timestamp(PG_FUNCTION_ARGS) int tz; struct pg_tm tm; fsec_t fsec; + int fprec; do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, - &tm, &fsec, NULL); + &tm, &fsec, &fprec, NULL); /* Use the specified time zone, if any. */ if (tm.tm_zone) @@ -3824,6 +3911,10 @@ to_timestamp(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + /* Use the specified fractional precision, if any. */ + if (fprec) + AdjustTimestampForTypmod(&result, fprec); + PG_RETURN_TIMESTAMP(result); } @@ -3842,7 +3933,7 @@ to_date(PG_FUNCTION_ARGS) fsec_t fsec; do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false, - &tm, &fsec, NULL); + &tm, &fsec, NULL, NULL); /* Prevent overflow in Julian-day routines */ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday)) @@ -3874,11 +3965,12 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, { struct pg_tm tm; fsec_t fsec; + int fprec = 0; int flags; - do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags); + do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &fprec, &flags); - *typmod = -1; /* TODO implement FF1, ..., FF9 */ + *typmod = fprec ? fprec : -1; /* fractional part precision */ if (flags & DCH_DATED) { @@ -4016,7 +4108,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, * do_to_timestamp: shared code for to_timestamp and to_date * * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm - * and fractional seconds. + * and fractional seconds and fractional precision. * * We parse 'fmt' into a list of FormatNodes, which is then passed to * DCH_from_char to populate a TmFromChar with the parsed contents of @@ -4033,7 +4125,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict, */ static void do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict, - struct pg_tm *tm, fsec_t *fsec, int *flags) + struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags) { FormatNode *format; TmFromChar tmfc; @@ -4275,6 +4367,8 @@ do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict, *fsec += tmfc.ms * 1000; if (tmfc.us) *fsec += tmfc.us; + if (fprec) + *fprec = tmfc.ff; /* fractional precision, if specified */ /* Range-check date fields according to bit mask computed above */ if (fmask != 0) diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index b2b4577333..fa27d74d4d 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -2786,6 +2786,92 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM'); Sun Dec 18 03:18:00 2011 PST (1 row) +SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------ + 1 | Fri Nov 02 12:34:56 2018 PDT + 2 | Fri Nov 02 12:34:56 2018 PDT + 3 | Fri Nov 02 12:34:56 2018 PDT + 4 | Fri Nov 02 12:34:56 2018 PDT + 5 | Fri Nov 02 12:34:56 2018 PDT + 6 | Fri Nov 02 12:34:56 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+-------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.1 2018 PDT + 3 | Fri Nov 02 12:34:56.1 2018 PDT + 4 | Fri Nov 02 12:34:56.1 2018 PDT + 5 | Fri Nov 02 12:34:56.1 2018 PDT + 6 | Fri Nov 02 12:34:56.1 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+--------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.12 2018 PDT + 4 | Fri Nov 02 12:34:56.12 2018 PDT + 5 | Fri Nov 02 12:34:56.12 2018 PDT + 6 | Fri Nov 02 12:34:56.12 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+---------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.123 2018 PDT + 5 | Fri Nov 02 12:34:56.123 2018 PDT + 6 | Fri Nov 02 12:34:56.123 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+----------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1234 2018 PDT + 5 | Fri Nov 02 12:34:56.1234 2018 PDT + 6 | Fri Nov 02 12:34:56.1234 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------------ + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1235 2018 PDT + 5 | Fri Nov 02 12:34:56.12345 2018 PDT + 6 | Fri Nov 02 12:34:56.12345 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + i | to_timestamp +---+------------------------------------- + 1 | Fri Nov 02 12:34:56.1 2018 PDT + 2 | Fri Nov 02 12:34:56.12 2018 PDT + 3 | Fri Nov 02 12:34:56.123 2018 PDT + 4 | Fri Nov 02 12:34:56.1235 2018 PDT + 5 | Fri Nov 02 12:34:56.12346 2018 PDT + 6 | Fri Nov 02 12:34:56.123456 2018 PDT +(6 rows) + +SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789" +-- FF7, FF8, FF9 are not supported +SELECT to_timestamp('123', 'FF7'); +ERROR: datetime formatting field "FF7" is not supported +SELECT to_timestamp('123', 'FF8'); +ERROR: datetime formatting field "FF8" is not supported +SELECT to_timestamp('123', 'FF9'); +ERROR: datetime formatting field "FF9" is not supported -- -- Check handling of multiple spaces in format and/or input -- diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index 4a2fabddd9..43759bd099 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -1597,6 +1597,29 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') | 2001 1 1 1 1 1 1 (65 rows) +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamp), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + to_char_12 | to_char +------------+---------------------------------------- + | 0 00 000 0000 00000 000000 000 000000 + | 7 78 780 7800 78000 780000 780 780000 + | 7 78 789 7890 78901 789010 789 789010 + | 7 78 789 7890 78901 789012 789 789012 +(4 rows) + +-- FF7-FF9 are not supported +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF7'); +ERROR: datetime formatting field "FF7" is not supported +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF8'); +ERROR: datetime formatting field "FF8" is not supported +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF9'); +ERROR: datetime formatting field "FF9" is not supported + -- timestamp numeric fields constructor SELECT make_timestamp(2014,12,28,6,30,45.887); make_timestamp diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 2340f30794..f4d1385196 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -1699,6 +1699,28 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') | 2001 1 1 1 1 1 1 (66 rows) +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamptz), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + to_char_12 | to_char +------------+---------------------------------------- + | 0 00 000 0000 00000 000000 000 000000 + | 7 78 780 7800 78000 780000 780 780000 + | 7 78 789 7890 78901 789010 789 789010 + | 7 78 789 7890 78901 789012 789 789012 +(4 rows) + +-- FF7-FF9 are not supported +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF7'); +ERROR: datetime formatting field "FF7" is not supported +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF8'); +ERROR: datetime formatting field "FF8" is not supported +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF9'); +ERROR: datetime formatting field "FF9" is not supported -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours SET timezone = '00:00'; SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM"; diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index e356dd563e..ef34323be4 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -402,6 +402,20 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM'); SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM'); +SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; +SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i; + +-- FF7, FF8, FF9 are not supported +SELECT to_timestamp('123', 'FF7'); +SELECT to_timestamp('123', 'FF8'); +SELECT to_timestamp('123', 'FF9'); + -- -- Check handling of multiple spaces in format and/or input -- diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index b7957cbb9a..f9d4ccb3ee 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -228,5 +228,18 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID') SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') FROM TIMESTAMP_TBL; +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamp), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + +-- FF7-FF9 are not supported +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF7'); +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF8'); +SELECT to_char('2018-11-02 12:34:56'::timestamp, 'FF9'); + -- timestamp numeric fields constructor SELECT make_timestamp(2014,12,28,6,30,45.887); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index f17d153fcc..8a8f95a7d6 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -248,6 +248,19 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID') SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID') FROM TIMESTAMPTZ_TBL; +SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 MS US') + FROM (VALUES + ('2018-11-02 12:34:56'::timestamptz), + ('2018-11-02 12:34:56.78'), + ('2018-11-02 12:34:56.78901'), + ('2018-11-02 12:34:56.78901234') + ) d(d); + +-- FF7-FF9 are not supported +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF7'); +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF8'); +SELECT to_char('2018-11-02 12:34:56'::timestamptz, 'FF9'); + -- Check OF, TZH, TZM with various zone offsets, particularly fractional hours SET timezone = '00:00'; SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM"; From 935397b001be21912a128781c4da241d69811735 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 16 Feb 2017 12:32:00 +0300 Subject: [PATCH 05/75] 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 788f88129b..1ef95c3cd4 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 94c3ea9ddb7fe1a3504f8c9af30d66a5873e8555 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 14:21:50 +0300 Subject: [PATCH 06/75] 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/.gitignore | 3 + src/backend/utils/adt/Makefile | 18 +- src/backend/utils/adt/jsonb.c | 6 +- src/backend/utils/adt/jsonpath.c | 421 +++++++++++++++ src/backend/utils/adt/jsonpath_exec.c | 421 +++++++++++++++ src/backend/utils/adt/jsonpath_gram.y | 281 ++++++++++ src/backend/utils/adt/jsonpath_scan.l | 513 +++++++++++++++++++ src/include/catalog/pg_proc.dat | 15 + src/include/catalog/pg_type.dat | 5 + src/include/lib/stringinfo.h | 6 + src/include/utils/.gitignore | 1 + 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 +++ src/tools/msvc/Mkvcbuild.pm | 2 + src/tools/msvc/Solution.pm | 18 + 24 files changed, 2543 insertions(+), 6 deletions(-) create mode 100644 src/backend/utils/adt/.gitignore 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 3a58bf6685..92c881af8a 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 df7e01f76d..fffc791ea8 100644 --- a/src/backend/lib/stringinfo.c +++ b/src/backend/lib/stringinfo.c @@ -312,3 +312,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/.gitignore b/src/backend/utils/adt/.gitignore new file mode 100644 index 0000000000..7fab054407 --- /dev/null +++ b/src/backend/utils/adt/.gitignore @@ -0,0 +1,3 @@ +/jsonpath_gram.h +/jsonpath_gram.c +/jsonpath_scan.c diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index 20eead1798..d335eec09a 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 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 name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \ @@ -32,6 +33,21 @@ 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 +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 0ae9d7b9c5..9843ecef09 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..ad4ceda6e0 --- /dev/null +++ b/src/backend/utils/adt/jsonpath.c @@ -0,0 +1,421 @@ +/*------------------------------------------------------------------------- + * + * 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) +{ + /* position from begining of jsonpath data */ + int32 pos = buf->len - VARHDRSZ; + int32 chld; + int32 next; + + check_stack_depth(); + CHECK_FOR_INTERRUPTS(); + + 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) + { + chld = flattenJsonPathParseItem(buf, item->next); + *(int32 *)(buf->data + next) = chld; + } + + 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..54e5a7d9d6 --- /dev/null +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -0,0 +1,421 @@ +/*------------------------------------------------------------------------- + * + * 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(); + CHECK_FOR_INTERRUPTS(); + + 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..bd4c4e2b78 --- /dev/null +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -0,0 +1,281 @@ +/*------------------------------------------------------------------------- + * + * 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 "miscadmin.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)); + + CHECK_FOR_INTERRUPTS(); + + 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 4026018ba9..eff15bb94b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9103,6 +9103,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 d295eae1b9..e7ae4ccc0c 100644 --- a/src/include/catalog/pg_type.dat +++ b/src/include/catalog/pg_type.dat @@ -441,6 +441,11 @@ typname => 'jsonb', typlen => '-1', typbyval => 'f', typcategory => 'U', typinput => 'jsonb_in', typoutput => 'jsonb_out', typreceive => 'jsonb_recv', typsend => 'jsonb_send', typalign => 'i', typstorage => 'x' }, +{ oid => '6050', array_type_oid => '6051', descr => 'JSON path', + typname => 'jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'U', + typarray => '_jsonpath', typinput => 'jsonpath_in', + typoutput => 'jsonpath_out', typreceive => '-', typsend => '-', + typalign => 'i', typstorage => 'x' }, { oid => '2970', array_type_oid => '2949', 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/.gitignore b/src/include/utils/.gitignore index 05cfa7a8d6..e0705e1aa7 100644 --- a/src/include/utils/.gitignore +++ b/src/include/utils/.gitignore @@ -3,3 +3,4 @@ /probes.h /errcodes.h /header-stamp +/jsonpath_gram.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 289c658483..4b4fc93c3a 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 bc43b18c62..64b07fc78e 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -156,6 +156,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; diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index b562044fa7..9b80e69e55 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -177,6 +177,8 @@ sub mkvcbuild 'src/backend/replication', 'repl_scanner.l', 'repl_gram.y', 'syncrep_scanner.l', 'syncrep_gram.y'); + $postgres->AddFiles('src/backend/utils/adt', 'jsonpath_scan.l', + 'jsonpath_gram.y'); $postgres->AddDefine('BUILDING_DLL'); $postgres->AddLibrary('secur32.lib'); $postgres->AddLibrary('ws2_32.lib'); diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm index 68cf812f01..9998e16249 100644 --- a/src/tools/msvc/Solution.pm +++ b/src/tools/msvc/Solution.pm @@ -329,6 +329,24 @@ sub GenerateFiles ); } + if (IsNewer( + 'src/backend/utils/adt/jsonpath_gram.h', + 'src/backend/utils/adt/jsonpath_gram.y')) + { + print "Generating jsonpath_gram.h...\n"; + chdir('src/backend/utils/adt'); + system('perl ../../../tools/msvc/pgbison.pl jsonpath_gram.y'); + chdir('../../../..'); + } + + if (IsNewer( + 'src/include/utils/jsonpath_gram.h', + 'src/backend/utils/adt/jsonpath_gram.h')) + { + copyFile('src/backend/utils/adt/jsonpath_gram.h', + 'src/include/utils/jsonpath_gram.h'); + } + if ($self->{options}->{python} && IsNewer( 'src/pl/plpython/spiexceptions.h', From 1b5227af7a5eea1c2f08cafb0d987a202a8fd19e Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 16:26:51 +0300 Subject: [PATCH 07/75] 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 ad4ceda6e0..72730b5b86 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -89,6 +89,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); } @@ -157,6 +165,7 @@ static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes) { JsonPathItem elem; + int i; check_stack_depth(); @@ -235,6 +244,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); } @@ -262,15 +281,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) @@ -326,6 +350,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); } @@ -356,6 +384,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 54e5a7d9d6..fdafd4e902 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -247,7 +247,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 @@ -265,21 +268,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) { @@ -299,7 +328,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 bd4c4e2b78..a08869d0c9 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 * *------------------------------------------------------------------------- */ @@ -158,6 +158,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 */ @@ -170,21 +188,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 '(' ')' @@ -192,8 +213,8 @@ makeItemExpression(List *path, JsonPathParseItem *right_expr) /* Grammar follows */ %% -result: - jsonpath { *result = $1; } +result: + jsonpath { *result = $1; } | /* EMPTY */ { *result = NULL; } ; @@ -209,9 +230,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: @@ -242,15 +263,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 c3db6621d6a321c63ba6454f893d59e85a328783 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Wed, 22 Feb 2017 19:20:27 +0300 Subject: [PATCH 08/75] Add passing params to jsonpath --- src/backend/utils/adt/jsonpath.c | 6 +- src/backend/utils/adt/jsonpath_exec.c | 382 ++++++++++++++++--- 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, 451 insertions(+), 85 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 72730b5b86..8f1f9c1900 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) { @@ -278,9 +279,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 fdafd4e902..72caa5e7ee 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -13,23 +13,134 @@ #include "postgres.h" #include "funcapi.h" #include "miscadmin.h" +#include "catalog/pg_type.h" #include "lib/stringinfo.h" #include "utils/builtins.h" +#include "utils/datum.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_UNDEFINED_OBJECT), + errmsg("could not find jsonpath variable '%s'", + 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_INVALID_PARAMETER_VALUE), + 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 +164,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 +208,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 +242,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 +272,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; @@ -155,25 +294,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; @@ -199,7 +338,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 @@ -221,11 +360,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: @@ -233,7 +372,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonbIterator *it; int32 r; - JsonbValue v, *pv; + JsonbValue v; bool hasNext; hasNext = jspGetNext(jsp, &elem); @@ -245,7 +384,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; @@ -260,9 +399,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)); } } } @@ -289,7 +426,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; @@ -314,7 +451,7 @@ recursiveExecute(JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonbIterator *it; int32 r; - JsonbValue v, *pv; + JsonbValue v; bool hasNext; hasNext = jspGetNext(jsp, &elem); @@ -326,7 +463,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; @@ -341,9 +478,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)); } } } @@ -355,17 +490,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); @@ -375,7 +520,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; @@ -386,17 +531,100 @@ 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_INVALID_PARAMETER_VALUE), + errmsg("json containing jsonpath variables is not an 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); + + /* Datums are copied from jsonb into the current memory context. */ + 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 = DatumGetPointer( + datumCopy(NumericGetDatum(v.val.numeric), false, -1)); + break; + case jbvBinary: + jpv->typid = JSONBOID; + jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v))); + break; + default: + elog(ERROR, "invalid jsonb value type"); + } + + 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); @@ -408,6 +636,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; @@ -421,12 +661,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"); @@ -451,3 +695,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 a08869d0c9..2264b149d4 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -193,8 +193,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 @@ -226,13 +225,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 eff15bb94b..68ee6a3f1b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9112,11 +9112,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..693eb3fb2a 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 jsonpath variable 'value' +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 d5c87916cff21418c0e74bd9a3bb5b33750aa38a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 25 Feb 2017 12:47:48 +0300 Subject: [PATCH 09/75] 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 2264b149d4..4d29eeb206 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -198,9 +198,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 @@ -281,18 +281,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); } @@ -303,8 +312,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: @@ -312,6 +323,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 39948d1e85dfcb5d16b23ef839ef4fc64550358e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 25 Feb 2017 23:55:32 +0300 Subject: [PATCH 10/75] 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 72caa5e7ee..dea2af3b05 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -102,6 +102,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 66c4c3e5fdd3bd9d72ba737f7ab77ceff319e092 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 12:59:27 +0300 Subject: [PATCH 11/75] 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 db0765cab6ffc4cf597a52da0ccff6a95549e7df Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 15:55:51 +0300 Subject: [PATCH 12/75] 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 8f1f9c1900..641ed09994 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -98,6 +98,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); } @@ -255,6 +263,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); } @@ -354,6 +379,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); } @@ -382,6 +411,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 dea2af3b05..ff7ee286a1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -296,6 +296,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) { @@ -526,6 +587,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 4d29eeb206..2fc05d4ac3 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -176,6 +176,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 */ @@ -292,9 +303,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 693eb3fb2a..a6b2fa1bfd 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 22a29d4ca625893589c690daa9750ca4b4cf1a55 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Feb 2017 15:04:06 +0300 Subject: [PATCH 13/75] 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 641ed09994..d73bf807b2 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -53,6 +53,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; @@ -67,13 +78,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; @@ -156,7 +165,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: @@ -165,11 +176,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) { @@ -208,33 +257,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: @@ -363,16 +420,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: @@ -392,13 +458,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); @@ -415,6 +479,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiAnyArray || v->type == jpiAnyKey || v->type == jpiIndexArray || + v->type == jpiFilter || v->type == jpiCurrent || v->type == jpiRoot ); @@ -432,7 +497,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); @@ -443,7 +519,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 ff7ee286a1..e481acd3ec 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -20,6 +20,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 @@ -189,101 +192,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* @@ -428,18 +522,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: @@ -560,12 +669,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)) @@ -580,11 +689,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: @@ -616,6 +728,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 2fc05d4ac3..cbab483dcb 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -122,7 +122,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; @@ -150,14 +164,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) { @@ -201,23 +207,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 */ @@ -239,52 +251,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: @@ -297,27 +344,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: @@ -327,26 +370,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 a6b2fa1bfd..fac620e08a 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 jsonpath variable 'value' -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 5711bd5fe2260512200f2ed319a6d92508f80da4 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 17:00:15 +0300 Subject: [PATCH 14/75] 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 d73bf807b2..bf3a7b793f 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -165,7 +165,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: @@ -246,8 +246,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 cbab483dcb..1bf83050d5 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -213,6 +213,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 @@ -264,12 +265,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 fac620e08a..a5f9dfc379 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 68901b7cd42e50d1361cc7169b2362655f9f5890 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Feb 2017 18:53:04 +0300 Subject: [PATCH 15/75] 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 bf3a7b793f..d5e18b31bc 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -294,6 +294,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 e481acd3ec..ef5822f66c 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -493,6 +493,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 1bf83050d5..5b90bd80ff 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -210,7 +210,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 @@ -244,6 +244,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); } @@ -286,7 +288,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 a5f9dfc379..1820a941d2 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 bd9902f95dec63faa42dbd4b72b40e87ddcf1e10 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Mon, 27 Feb 2017 21:02:51 +0300 Subject: [PATCH 16/75] implement jsonpath EXISTS clause --- src/backend/utils/adt/jsonpath.c | 63 ++++++++++++++++---- 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, 116 insertions(+), 34 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d5e18b31bc..4046f9ff64 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) { /* position from begining of jsonpath data */ int32 pos = buf->len - VARHDRSZ; @@ -38,17 +39,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: @@ -72,32 +86,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, @@ -121,7 +153,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item) if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next); + chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot); *(int32 *)(buf->data + next) = chld; } @@ -147,7 +179,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); @@ -300,6 +332,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, '@'); @@ -441,6 +479,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: @@ -467,6 +506,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiFilter || v->type == jpiNot || v->type == jpiIsUnknown || + v->type == jpiExists || v->type == jpiPlus || v->type == jpiMinus ); @@ -487,6 +527,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 ef5822f66c..cc623119aa 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -740,6 +740,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 5b90bd80ff..59b4b3fbc6 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -210,7 +210,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 @@ -218,7 +218,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 */ @@ -243,9 +243,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); } @@ -254,14 +251,6 @@ scalar_value: | VARIABLE_P { $$ = makeItemVariable(&$1); } ; -/* -numeric: - NUMERIC_P { $$ = makeItemNumeric(&$1); } - | INT_P { $$ = makeItemNumeric(&$1); } - | VARIABLE_P { $$ = makeItemVariable(&$1); } - ; -*/ - jsonpath: expr ; @@ -276,8 +265,8 @@ comp_op: ; delimited_predicate: - '(' predicate ')' { $$ = $2; } -// | EXISTS '(' relative_path ')' { $$ = makeItemUnary(jpiExists, $2); } + '(' predicate ')' { $$ = $2; } + | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); } ; predicate: @@ -312,8 +301,6 @@ unary_expr: | '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); } ; -// | '(' expr ')' { $$ = $2; } - expr: unary_expr { $$ = $1; } | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); } @@ -372,6 +359,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 1820a941d2..b35123ddfa 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 d8454e6d0cc12ab79c90ef4cf96e0e71bb7bbf9f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 00:25:03 +0300 Subject: [PATCH 17/75] 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 cc623119aa..41a80b7b99 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -464,19 +464,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: @@ -495,15 +503,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 b35123ddfa..d67d9d7fd2 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 0a1a25df57c5b90f761174626f6fbfb076bfbc14 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 01:29:41 +0300 Subject: [PATCH 18/75] 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 41a80b7b99..6981a43155 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -20,8 +20,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***************************/ @@ -130,7 +137,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) { @@ -150,7 +157,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"); @@ -306,7 +313,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; @@ -314,17 +321,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; @@ -356,14 +362,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; @@ -391,10 +397,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; @@ -425,7 +428,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; } @@ -440,7 +443,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; @@ -452,7 +455,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; @@ -463,33 +467,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; @@ -503,7 +507,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: @@ -520,7 +524,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 @@ -557,11 +561,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: @@ -581,7 +585,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; @@ -623,7 +627,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; @@ -660,7 +664,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; @@ -687,12 +691,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 { @@ -704,11 +708,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; @@ -721,7 +725,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; } @@ -735,7 +739,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); @@ -743,7 +747,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: @@ -754,7 +758,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; @@ -768,16 +772,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 1f95a97e868b3e84bb9b4595d2dd2cf1ba1fa628 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 13:08:08 +0300 Subject: [PATCH 19/75] 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 6981a43155..0de1fb2340 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -386,6 +386,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) { @@ -693,6 +771,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 58d3a4974fb327cce5b240637fb5f51dd2c2cbbf Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 12:18:30 +0300 Subject: [PATCH 20/75] 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 d67d9d7fd2..d8411b22ad 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 ff9c9a0c2b9a8b40f5e161f5e850e962877a620e Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 13:17:02 +0300 Subject: [PATCH 21/75] 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 0de1fb2340..080d898a35 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -216,7 +216,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 59b4b3fbc6..1557f80f13 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -216,7 +216,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 @@ -271,7 +271,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 { $$ = ...; } @@ -301,13 +301,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 56e011138a7845100a897805ffec24e197a87531 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 13:45:43 +0300 Subject: [PATCH 22/75] 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 4046f9ff64..b6eaf8c656 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -95,7 +95,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFilter: case jpiIsUnknown: case jpiNot: - case jpiPlus: case jpiMinus: case jpiExists: { @@ -482,7 +481,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; @@ -507,7 +505,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 080d898a35..8d18d51bcc 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -402,19 +402,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); @@ -422,20 +434,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; @@ -776,6 +797,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 1557f80f13..9e41babd32 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -217,7 +217,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 */ @@ -231,6 +231,7 @@ makeAny(int first, int last) %right NOT_P %left '+' '-' %left '*' '/' '%' +%left UMINUS %nonassoc '(' ')' /* Grammar follows */ @@ -295,19 +296,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 d8411b22ad..17f26b3fe9 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 d2522d13bce8d86a3d0e28f4da2b51e004886cf4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 15:40:09 +0300 Subject: [PATCH 23/75] 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 b6eaf8c656..def431a096 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -95,6 +95,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFilter: case jpiIsUnknown: case jpiNot: + case jpiPlus: case jpiMinus: case jpiExists: { @@ -480,6 +481,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); @@ -505,6 +507,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 8d18d51bcc..222f665569 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -387,8 +387,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; @@ -402,29 +402,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); @@ -434,29 +423,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; @@ -485,7 +467,7 @@ executeArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return jperOk; } -static JsonbValue* +static JsonbValue * copyJsonbValue(JsonbValue *src) { JsonbValue *dst = palloc(sizeof(*dst)); @@ -495,6 +477,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) @@ -797,8 +838,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 9e41babd32..896f5adc95 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -303,7 +303,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 17f26b3fe9..cc5b72ebc4 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 51ec4e451d024f72a4a62974c8e2293bbc2780a0 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 15:54:51 +0300 Subject: [PATCH 24/75] 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 def431a096..deb87af62d 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -47,8 +47,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: @@ -56,14 +57,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: @@ -86,9 +89,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; @@ -104,7 +107,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; @@ -133,19 +136,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 896f5adc95..216f28b617 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -58,8 +58,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; @@ -71,8 +71,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; } @@ -94,7 +94,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; } @@ -103,7 +104,7 @@ static JsonPathParseItem* makeItemBool(bool val) { JsonPathParseItem *v = makeItemType(jpiBool); - v->boolean = val; + v->value.boolean = val; return v; } @@ -113,8 +114,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; } @@ -130,15 +131,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; } @@ -172,12 +173,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; } @@ -187,8 +188,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; } @@ -205,7 +206,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; } @@ -216,11 +217,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 @@ -238,7 +238,7 @@ makeAny(int first, int last) %% result: - jsonpath { *result = $1; } + expr { *result = $1; } | /* EMPTY */ { *result = NULL; } ; @@ -252,10 +252,6 @@ scalar_value: | VARIABLE_P { $$ = makeItemVariable(&$1); } ; -jsonpath: - expr - ; - comp_op: EQUAL_P { $$ = jpiEqual; } | NOTEQUAL_P { $$ = jpiNotEqual; } @@ -272,16 +268,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: @@ -365,23 +363,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 48d9fc9d87d0259f0e5f2700051166e8eaba2487 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 16:26:59 +0300 Subject: [PATCH 25/75] add header for jsonpath type --- src/backend/utils/adt/jsonpath.c | 8 +++++--- src/include/utils/jsonpath.h | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index deb87af62d..e0dea3e353 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -24,7 +24,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, bool forbiddenRoot) { /* position from begining of jsonpath data */ - int32 pos = buf->len - VARHDRSZ; + int32 pos = buf->len - JSONPATH_HDRSZ; int32 chld; int32 next; @@ -176,7 +176,7 @@ jsonpath_in(PG_FUNCTION_ARGS) initStringInfo(&buf); enlargeStringInfo(&buf, 4 * len /* estimation */); - appendStringInfoSpaces(&buf, VARHDRSZ); + appendStringInfoSpaces(&buf, JSONPATH_HDRSZ); if (!jsonpath) ereport(ERROR, @@ -187,6 +187,7 @@ jsonpath_in(PG_FUNCTION_ARGS) res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); + res->header = JSONPATH_VERSION; PG_RETURN_JSONPATH_P(res); } @@ -429,7 +430,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 506caaccb8e772ccb367cb4d0103b599b6055e83 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 16:40:39 +0300 Subject: [PATCH 26/75] 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 e0dea3e353..44df541118 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -361,11 +361,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; @@ -373,18 +373,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); @@ -462,11 +462,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: @@ -481,8 +481,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: @@ -490,15 +490,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); @@ -517,7 +517,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMinus ); - jspInitByBuffer(a, v->base, v->arg); + jspInitByBuffer(a, v->base, v->content.arg); } bool @@ -564,7 +564,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 @@ -586,7 +586,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 @@ -594,7 +594,7 @@ jspGetBool(JsonPathItem *v) { Assert(v->type == jpiBool); - return (bool)*v->value.data; + return (bool)*v->content.value.data; } Numeric @@ -602,7 +602,7 @@ jspGetNumeric(JsonPathItem *v) { Assert(v->type == jpiNumeric); - return (Numeric)v->value.data; + return (Numeric)v->content.value.data; } char* @@ -615,6 +615,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 222f665569..2a01ec2e8a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -756,11 +756,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; @@ -872,7 +872,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) { @@ -892,8 +892,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 3f1e72ba0e8e90e1eaad8b9e3a8292e3a3a19620 Mon Sep 17 00:00:00 2001 From: Teodor Sigaev Date: Tue, 28 Feb 2017 18:27:19 +0300 Subject: [PATCH 27/75] 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 44df541118..251d2b22cb 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) @@ -35,6 +39,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) @@ -85,6 +94,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)); @@ -412,6 +426,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; \ @@ -427,6 +445,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) { @@ -434,6 +455,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 2a01ec2e8a..1d8633bfe2 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -13,12 +13,14 @@ #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/datum.h" #include "utils/json.h" #include "utils/jsonpath.h" +#include "utils/varlena.h" typedef struct JsonPathExecContext { @@ -32,6 +34,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) { @@ -136,6 +141,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) { @@ -165,6 +173,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) @@ -200,31 +214,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) { @@ -236,14 +229,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 @@ -273,13 +280,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; } @@ -536,6 +541,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) @@ -552,6 +560,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) @@ -594,12 +605,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(); CHECK_FOR_INTERRUPTS(); @@ -612,10 +634,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: @@ -628,7 +655,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: @@ -648,7 +675,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) @@ -681,6 +708,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) { @@ -690,7 +718,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); } @@ -714,7 +742,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); @@ -751,7 +778,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, if (JsonbType(jb) == jbvArray) { JsonbValue *v; - bool hasNext; int i; hasNext = jspGetNext(jsp, &elem); @@ -793,7 +819,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); @@ -920,6 +945,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) { @@ -953,6 +981,9 @@ returnNULL(void *arg, bool *isNull) return Int32GetDatum(0); } +/* + * Convert jsonb object into list of vars for executor + */ static List* makePassingVars(Jsonb *jb) { From 5bd6f1128d035a8297be26267fc2591205374b69 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:18:40 +0300 Subject: [PATCH 28/75] 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 1d8633bfe2..d8297dc87b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -32,6 +32,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***************************/ /* @@ -124,11 +134,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 */ @@ -958,10 +964,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 1f9f99f4afdaece07c75787b2ec450760e0aad7d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:19:48 +0300 Subject: [PATCH 29/75] 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 d8297dc87b..b6f976fac9 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -42,6 +42,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 70a0d6e36656c1ea2eea6b116ae9a488c660ab3c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 28 Feb 2017 04:03:41 +0300 Subject: [PATCH 30/75] 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 | 26 +- 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, 517 insertions(+), 122 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 251d2b22cb..fc14b8265e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -183,8 +183,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); @@ -197,11 +197,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); } @@ -418,6 +420,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); @@ -451,7 +456,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 b6f976fac9..1235062826 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -32,6 +32,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) { @@ -334,6 +337,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) { @@ -347,14 +408,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) { @@ -402,7 +463,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; @@ -426,16 +487,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); @@ -443,7 +505,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); @@ -451,7 +513,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; @@ -489,16 +551,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) @@ -509,10 +561,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; @@ -535,7 +587,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); @@ -633,8 +685,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; @@ -677,7 +729,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; @@ -692,7 +744,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) @@ -720,6 +772,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: @@ -771,7 +833,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) @@ -789,6 +851,8 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } } + else + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; case jpiIndexArray: @@ -812,7 +876,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) @@ -829,6 +893,8 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, } } } + else + res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; case jpiAnyKey: if (JsonbType(jb) == jbvObject) @@ -848,7 +914,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) @@ -866,6 +932,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: @@ -940,7 +1011,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: @@ -962,6 +1048,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 */ @@ -972,13 +1161,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); } @@ -1064,6 +1268,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) { @@ -1080,8 +1345,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); } @@ -1099,7 +1364,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; @@ -1123,8 +1388,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 216f28b617..c51d588694 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -201,23 +201,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 @@ -226,6 +230,8 @@ makeAny(int first, int last) %type comp_op +%type mode + %left OR_P %left AND_P %right NOT_P @@ -238,10 +244,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); } @@ -362,6 +378,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..cfc297ab58 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -20,12 +20,13 @@ typedef struct { - int32 vl_len_;/* varlena header (do not touch directly!) */ - uint32 header; /* just version, other bits are reservedfor future use */ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint32 header; /* version and flags (see below) */ char data[FLEXIBLE_ARRAY_MEMBER]; } 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 cc5b72ebc4..ddc0ab5c61 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 50aa0df9800c2448e3035b3c6e6f6deaa17dd305 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 6 Mar 2017 23:36:13 +0300 Subject: [PATCH 31/75] 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 fc14b8265e..f0916330f3 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -165,6 +165,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); } @@ -402,6 +411,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); } @@ -487,6 +520,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: @@ -563,7 +604,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 1235062826..78064bef97 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -222,6 +222,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) { @@ -1041,6 +1104,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); } @@ -1135,7 +1429,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 c51d588694..5e20631805 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -209,7 +209,7 @@ makeAny(int first, int last) List *indexs; /* list of integers */ JsonPathParseItem *value; JsonPathParseResult *result; - int optype; + JsonPathItemType optype; bool boolean; } @@ -218,6 +218,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 @@ -228,10 +230,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 @@ -366,20 +371,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 cfc297ab58..34e7b197d0 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 ddc0ab5c61..62a75abe13 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 0c756522e09c5d71b636bb4c583b87aec3bb2c9b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 6 Mar 2017 23:37:10 +0300 Subject: [PATCH 32/75] 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 78064bef97..4b35a9d87e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -209,11 +209,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 478ad69a3154b2b2f29bc52e6f09f86933e3ab61 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:50:12 +0300 Subject: [PATCH 33/75] 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 f0916330f3..8b25bab286 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -50,11 +50,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)); @@ -62,18 +57,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; @@ -128,10 +115,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) @@ -596,6 +579,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 || @@ -605,6 +592,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 4b35a9d87e..582dbc1853 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1096,12 +1096,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 62a75abe13..789028f582 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 db400294e27dcb57a6e8629c36a5e951f669a6d4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:51:41 +0300 Subject: [PATCH 34/75] 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 8b25bab286..dcd1fa0c71 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -259,8 +259,11 @@ operationPriority(JsonPathItemType op) case jpiDiv: case jpiMod: return 4; - default: + case jpiPlus: + case jpiMinus: return 5; + default: + return 6; } } @@ -327,6 +330,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 29fa653584d0fcbe91a81a9a63cc5cef122ac5dd Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 7 Mar 2017 13:54:02 +0300 Subject: [PATCH 35/75] 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 dcd1fa0c71..e57c526057 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -132,13 +132,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, @@ -384,11 +410,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; @@ -477,7 +514,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) \ @@ -562,7 +599,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); @@ -699,3 +737,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 582dbc1853..870d90d63d 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -737,6 +737,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 @@ -921,39 +952,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 5e20631805..080ceba929 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -178,7 +178,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; } @@ -225,10 +232,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 @@ -332,22 +340,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 34e7b197d0..88563303f7 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 789028f582..9a4bbdc716 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 6aaf389b2b7655c8e5d57a6ca83ed2cc29d2497a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 8 Mar 2017 00:07:40 +0300 Subject: [PATCH 36/75] 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 e57c526057..25e8ab6f9c 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; @@ -90,9 +90,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; @@ -110,7 +114,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || - forbiddenRoot); + forbiddenRoot, + insideArraySubscript); *(int32*)(buf->data + arg) = chld; } break; @@ -131,6 +136,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; @@ -150,12 +161,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; @@ -189,7 +200,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot); + chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot, + insideArraySubscript); *(int32 *)(buf->data + next) = chld; } @@ -215,7 +227,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); @@ -400,6 +412,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; @@ -563,6 +578,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiDouble: case jpiDatetime: case jpiKeyValue: + case jpiLast: break; case jpiKey: case jpiString: @@ -646,6 +662,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 870d90d63d..b0ad4ae711 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -26,6 +26,7 @@ typedef struct JsonPathExecContext { List *vars; bool lax; + int innermostArraySize; /* for LAST array index evaluation */ } JsonPathExecContext; static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, @@ -952,9 +953,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++) @@ -1034,10 +1038,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) { @@ -1555,6 +1598,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 080ceba929..e49cdd2d52 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -224,7 +224,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 @@ -315,6 +315,7 @@ path_primary: scalar_value { $$ = $1; } | '$' { $$ = makeItemType(jpiRoot); } | '@' { $$ = makeItemType(jpiCurrent); } + | LAST_P { $$ = makeItemType(jpiLast); } ; accessor_expr: @@ -397,6 +398,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 88563303f7..993b730744 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 9a4bbdc716..445c643bf9 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 34c7b12ebf2826d50d264e4a159d34b33474d0de Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 9 Mar 2017 13:30:20 +0300 Subject: [PATCH 37/75] 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 25e8ab6f9c..75576fcaac 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -77,6 +77,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiMul: case jpiDiv: case jpiMod: + case jpiStartsWith: { int32 left, right; @@ -269,6 +270,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); } @@ -289,6 +292,7 @@ operationPriority(JsonPathItemType op) case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: + case jpiStartsWith: return 2; case jpiAdd: case jpiSub: @@ -354,6 +358,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); @@ -602,6 +607,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; @@ -670,7 +676,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) @@ -697,7 +704,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); @@ -719,7 +727,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 b0ad4ae711..b4954a8b86 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -769,6 +769,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 @@ -1466,6 +1537,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 e49cdd2d52..2fc419e5c0 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -224,7 +224,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 @@ -232,7 +232,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 @@ -302,15 +302,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); } @@ -399,6 +402,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 993b730744..888b1e5575 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 445c643bf9..70e9ec47b8 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 1393b8777252a4c02d3e297ecfce9dbb488e8321 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 6 Sep 2017 03:21:37 +0300 Subject: [PATCH 38/75] 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 75576fcaac..c355bd8cd7 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -101,6 +101,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: @@ -373,6 +396,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) @@ -611,6 +666,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 b4954a8b86..913af70f00 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/datum.h" #include "utils/json.h" @@ -840,6 +841,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 @@ -1540,6 +1607,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 2fc419e5c0..439a8f5080 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -15,8 +15,10 @@ #include "postgres.h" #include "fmgr.h" +#include "catalog/pg_collation.h" #include "miscadmin.h" #include "nodes/pg_list.h" +#include "regex/regex.h" #include "utils/builtins.h" #include "utils/jsonpath.h" @@ -201,6 +203,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 */ @@ -224,7 +273,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 @@ -304,9 +353,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: @@ -404,6 +453,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 171fcc8a44..4ba9d6010f 100644 --- a/src/backend/utils/adt/regexp.c +++ b/src/backend/utils/adt/regexp.c @@ -133,7 +133,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); @@ -339,7 +339,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 888b1e5575..edcfff58fb 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 70e9ec47b8..06f66388d2 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 c83a3ea55cb2b734df540be932a9d1b9d1c11335 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Mar 2017 18:42:09 +0300 Subject: [PATCH 39/75] Add jbvDatetime JsonbValue type --- src/backend/utils/adt/jsonb_util.c | 20 ++++++++++++++++++++ src/include/utils/jsonb.h | 21 ++++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c index 713631b04f..41cce1f29e 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..c2c4eaa77e 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -238,7 +238,14 @@ enum jbvType jbvArray = 0x10, jbvObject, /* Binary (i.e. struct Jsonb) jbvArray/jbvObject */ - jbvBinary + jbvBinary, + /* + * Virtual types. + * + * These types are used only for in-memory JSON processing and serialized + * into JSON strings when outputted to json/jsonb. + */ + jbvDatetime = 0x20, }; /* @@ -279,11 +286,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 f9e4061ac76187d647cf70d010e0a2947c646c6a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 10 Mar 2017 18:43:43 +0300 Subject: [PATCH 40/75] 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 c355bd8cd7..8fce409d0f 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -124,6 +124,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: @@ -215,7 +224,6 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiFloor: case jpiCeiling: case jpiDouble: - case jpiDatetime: case jpiKeyValue: break; default: @@ -540,7 +548,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); @@ -636,7 +650,6 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiFloor: case jpiCeiling: case jpiDouble: - case jpiDatetime: case jpiKeyValue: case jpiLast: break; @@ -678,6 +691,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: @@ -703,7 +717,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 913af70f00..34f1e0a425 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -19,6 +19,7 @@ #include "regex/regex.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" #include "utils/varlena.h" @@ -143,6 +144,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); @@ -257,13 +268,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"; @@ -299,6 +321,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) { @@ -331,6 +465,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"); } @@ -370,6 +519,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; } @@ -907,6 +1070,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 @@ -1521,7 +1709,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 439a8f5080..d0a3804c03 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 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 @@ -424,9 +424,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); } ; @@ -464,7 +471,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 06f66388d2..149362b1fb 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 6ee595c21ea6a4d02865b1d47e7e6249fedf9a54 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 23 Mar 2017 01:27:58 +0300 Subject: [PATCH 41/75] 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 8fce409d0f..340f34896e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -745,6 +745,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 34f1e0a425..8d50591a57 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -712,6 +712,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, Datum ldatum; Datum rdatum; Datum res; + bool hasNext; jspGetLeftArg(jsp, &elem); @@ -743,7 +744,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); @@ -774,6 +777,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; @@ -787,6 +793,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathItem elem; List *seq = NIL; ListCell *lc; + bool hasNext; jspGetArg(jsp, &elem); jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq); @@ -796,6 +803,8 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, jper = jperNotFound; + hasNext = jspGetNext(jsp, &elem); + foreach(lc, seq) { JsonbValue *val = lfirst(lc); @@ -806,12 +815,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) @@ -832,7 +839,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 d0a3804c03..f5da0bbac8 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -147,18 +147,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; @@ -373,6 +378,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 149362b1fb..464fdd94a6 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 f66f26c67c729cc687a63292b277d6f5c4af657e Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Mar 2017 13:41:27 +0300 Subject: [PATCH 42/75] 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 340f34896e..39afea4e47 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; @@ -92,11 +92,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; } @@ -119,7 +119,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; } @@ -147,7 +147,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = flattenJsonPathParseItem(buf, item->value.arg, item->type == jpiFilter || - forbiddenRoot, + allowCurrent, insideArraySubscript); *(int32*)(buf->data + arg) = chld; } @@ -155,16 +155,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"))); @@ -232,7 +228,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot, + chld = flattenJsonPathParseItem(buf, item->next, allowCurrent, insideArraySubscript); *(int32 *)(buf->data + next) = chld; } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 8d50591a57..f4d6d4ee7e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -28,6 +28,7 @@ typedef struct JsonPathExecContext { List *vars; bool lax; + JsonbValue *root; /* for $ evaluation */ int innermostArraySize; /* for LAST array index evaluation */ } JsonPathExecContext; @@ -1231,6 +1232,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)) { @@ -1493,19 +1497,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); @@ -2050,12 +2041,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 464fdd94a6..e85143d3f7 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 216e5617fbd8533c3dc96aa295f3a3775d1c0ad5 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 27 Mar 2017 18:05:41 +0300 Subject: [PATCH 43/75] 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 39afea4e47..673dbd214e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -723,7 +723,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 f4d6d4ee7e..3555c9b9f4 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -576,6 +576,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) @@ -778,12 +805,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 @@ -791,6 +813,7 @@ executeUnaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, List **found) { JsonPathExecResult jper; + JsonPathExecResult jper2; JsonPathItem elem; List *seq = NIL; ListCell *lc; @@ -809,10 +832,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) { @@ -825,8 +847,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: @@ -840,24 +860,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; } } @@ -901,25 +912,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; } @@ -1206,19 +1214,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) { @@ -1236,36 +1235,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) { @@ -1280,25 +1272,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; } } } @@ -1367,25 +1347,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)) @@ -1428,13 +1396,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: @@ -1451,25 +1413,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; } } } @@ -1503,10 +1453,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: { @@ -1515,19 +1463,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) @@ -1561,21 +1500,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: @@ -1586,12 +1526,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: @@ -1616,12 +1551,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: @@ -1657,12 +1587,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); @@ -1717,12 +1642,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: @@ -1822,10 +1742,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: @@ -1893,18 +1810,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 edcfff58fb..3141f48120 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 c5bae2ae6b916e8d7cf39fe3590824a4f6cbc075 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 30 Mar 2017 16:33:39 +0300 Subject: [PATCH 44/75] Introduce JsonValueList for jsonpath --- src/backend/utils/adt/jsonpath_exec.c | 221 +++++++++++++++++++------- src/include/utils/jsonpath.h | 8 +- 2 files changed, 168 insertions(+), 61 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index 3555c9b9f4..fb70969be7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -32,12 +32,91 @@ 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 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 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) @@ -579,7 +658,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; @@ -598,35 +677,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)) @@ -638,11 +716,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; @@ -656,10 +734,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; @@ -673,14 +751,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: @@ -727,12 +804,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; @@ -753,10 +830,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); @@ -764,7 +843,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); @@ -810,13 +889,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); @@ -829,10 +909,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); @@ -881,7 +959,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; @@ -942,17 +1020,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); @@ -974,9 +1052,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; @@ -987,10 +1066,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); @@ -1003,9 +1082,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) @@ -1045,8 +1123,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; @@ -1066,13 +1145,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) @@ -1142,7 +1220,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; @@ -1483,7 +1561,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 @@ -1492,7 +1570,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: @@ -1836,7 +1914,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) { @@ -1909,7 +1987,7 @@ wrapItem(JsonbValue *jbv) static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, - List **found) + JsonValueList *found) { check_stack_depth(); @@ -1947,7 +2025,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; @@ -1966,13 +2044,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); @@ -2159,7 +2237,7 @@ static Datum jsonb_jsonpath_query(FunctionCallInfo fcinfo) { FuncCallContext *funcctx; - List *found = NIL; + List *found; JsonbValue *v; ListCell *c; @@ -2170,6 +2248,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); @@ -2185,7 +2264,7 @@ jsonb_jsonpath_query(FunctionCallInfo fcinfo) PG_FREE_IF_COPY(jp, 1); - funcctx->user_fctx = found; + funcctx->user_fctx = JsonValueListGetList(&found); MemoryContextSwitchTo(oldcontext); } @@ -2215,3 +2294,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 3141f48120..416e53081f 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 c07afbe310a827a0c95e3f94b3352da633d8c419 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 14:02:18 +0300 Subject: [PATCH 45/75] 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 fb70969be7..2ae9a0e322 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -39,7 +39,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); @@ -293,7 +293,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; @@ -513,7 +513,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; @@ -1947,7 +1947,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; @@ -1985,12 +1985,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 1073039872034e547c49b1b1a0c43911f8245e61 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:28:51 +0300 Subject: [PATCH 46/75] 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 2ae9a0e322..04c1454d94 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -526,9 +526,6 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) return jperError; } - if (jb1->type == jbvBinary) - return jperError; - switch (jb1->type) { case jbvNull: @@ -560,8 +557,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; @@ -1284,6 +1287,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); @@ -1339,18 +1346,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; @@ -1359,6 +1364,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); @@ -1370,6 +1397,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 */ @@ -1418,14 +1446,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; @@ -1483,6 +1513,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); @@ -1535,25 +1569,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); @@ -1829,6 +1868,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, else { int32 r; + JsonbValue bin; JsonbValue key; JsonbValue val; JsonbValue obj; @@ -1839,7 +1879,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; @@ -1856,6 +1898,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) @@ -1918,24 +1963,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; } @@ -2305,10 +2369,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 aef145d249d0ad66fcb1afd8ce39e8e3eea6eef4 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 13:04:51 +0300 Subject: [PATCH 47/75] 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 04c1454d94..dc026e92a4 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -43,7 +43,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); @@ -1958,51 +1958,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 534b2a17998422687155667d7bff83cab7d76a76 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 15:18:35 +0300 Subject: [PATCH 48/75] 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 673dbd214e..f6b974f07c 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -748,6 +748,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 dc026e92a4..c782c71748 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -43,6 +43,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); @@ -1211,6 +1214,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 @@ -1223,7 +1254,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; @@ -1235,7 +1266,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; @@ -1246,27 +1277,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; @@ -1277,11 +1310,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) @@ -1548,6 +1583,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: @@ -1562,7 +1598,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 @@ -1611,6 +1647,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: @@ -1946,9 +1984,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); @@ -1970,7 +2010,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; @@ -1990,7 +2030,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) @@ -2009,7 +2049,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); } /* @@ -2085,7 +2125,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 f5da0bbac8..3ca8c059b7 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -287,6 +287,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 @@ -311,7 +312,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; @@ -319,6 +320,11 @@ result: | /* EMPTY */ { *result = NULL; } ; +expr_or_predicate: + expr { $$ = $1; } + | predicate { $$ = $1; } + ; + mode: STRICT_P { $$ = false; } | LAX_P { $$ = true; } @@ -379,6 +385,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 e85143d3f7..bf8052c9d1 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 0f0abe3011053f30bc93549a1f05dde8124418a2 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 31 Mar 2017 17:48:19 +0300 Subject: [PATCH 49/75] 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 c782c71748..ea45ad2f63 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2372,6 +2372,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 68ee6a3f1b..b21dfcdfa7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9124,6 +9124,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 bf8052c9d1..3dfa85ff8e 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 d590abf98887d5485339767c365409eb0eb0915c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 12 May 2017 00:59:23 +0300 Subject: [PATCH 50/75] 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 ce23c2f0aa..8443a858c4 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3195,5 +3195,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 b21dfcdfa7..25a34810f9 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9110,10 +9110,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' }, @@ -9124,7 +9124,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 3dfa85ff8e..02bcae968f 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 6480f676de2a7560cdd01fde1b3003cb0a1d80fa Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Mon, 2 Oct 2017 17:51:01 +0300 Subject: [PATCH 51/75] 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 8443a858c4..bf8d3d9bc4 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3197,14 +3197,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 25a34810f9..7646b7d161 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9111,24 +9111,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 02bcae968f..d032a53059 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 jsonpath variable 'value' -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 8a0304f83f5ed329add6838748939d7dba87e405 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 24 Jan 2018 01:39:44 +0300 Subject: [PATCH 52/75] 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 ea45ad2f63..9eec6ac1d5 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -2480,6 +2480,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 bf8d3d9bc4..a18eefd2aa 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3206,5 +3206,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 7646b7d161..31ddc30611 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9117,6 +9117,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' }, @@ -9124,6 +9127,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 d032a53059..a5a685557e 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 964a27744ca3331d3717c88f91eb7bcc6c49b363 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 15 Nov 2017 03:20:15 +0300 Subject: [PATCH 53/75] 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 3ca8c059b7..f6d4e2136a 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -275,7 +275,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 @@ -452,7 +452,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 a5a685557e..5da1ee0b1f 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 5214cc4983e6254d4fc6e33b22014980b0dc2844 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Sat, 20 Jan 2018 00:48:02 +0300 Subject: [PATCH 54/75] 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 f6d4e2136a..6d371b0836 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -434,7 +434,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 5da1ee0b1f..9bd9c00df6 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 eb3ba153faebbc5523c6b9a0805bcddc628f237c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 13 Feb 2018 23:16:34 +0300 Subject: [PATCH 55/75] 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 6d371b0836..52cb380a56 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -284,7 +284,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 @@ -351,21 +351,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); } ; @@ -385,29 +385,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 485bc16fd7e97fcca9037f2d3398ba5feca236bf Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 15 Feb 2018 20:29:42 +0300 Subject: [PATCH 56/75] 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 9eec6ac1d5..252c3bbb3e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -812,6 +812,7 @@ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { + MemoryContext mcxt = CurrentMemoryContext; JsonPathExecResult jper; JsonPathItem elem; JsonValueList lseq = { 0 }; @@ -820,6 +821,7 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *rval; JsonbValue lvalbuf; JsonbValue rvalbuf; + PGFunction func; Datum ldatum; Datum rdatum; Datum res; @@ -868,23 +870,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 9bd9c00df6..3cbe90934b 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 8cba26dcab7f54350513e95981efb1488bc8291d Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 02:45:32 +0300 Subject: [PATCH 57/75] 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 f7d4a504c9..ec570f2684 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -3960,8 +3960,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; @@ -3971,6 +3971,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, &fprec, &flags); *typmod = fprec ? fprec : -1; /* fractional part precision */ + *tz = 0; if (flags & DCH_DATED) { @@ -3979,20 +3980,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"))); @@ -4056,20 +4064,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 f47a498228..f4dda6b9e2 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 9843ecef09..00a7f3a293 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 41cce1f29e..5e600e0b09 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 f6b974f07c..35e4d6cafd 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -78,6 +78,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiDiv: case jpiMod: case jpiStartsWith: + case jpiDatetime: { int32 left, right; @@ -91,13 +92,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; @@ -124,15 +128,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: @@ -545,10 +540,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; @@ -672,6 +674,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; @@ -687,7 +690,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: @@ -713,8 +715,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); @@ -794,6 +795,7 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || + v->type == jpiDatetime || v->type == jpiStartsWith ); @@ -817,6 +819,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 252c3bbb3e..9d7b8827f7 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -235,6 +235,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: @@ -1211,16 +1212,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(); @@ -1826,83 +1833,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; @@ -1918,6 +1937,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 52cb380a56..d79e1c4cf4 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -286,7 +286,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 @@ -433,12 +433,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 c2c4eaa77e..9c47f1fc12 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -292,6 +292,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 3cbe90934b..1e82314732 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 0864532e9df9f230f9a0299d5e7e216b6d788072 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 8 Mar 2018 01:37:57 +0300 Subject: [PATCH 58/75] 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 35e4d6cafd..b7be28675e 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -508,16 +508,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 9d7b8827f7..99d06e7fe9 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1016,7 +1016,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 d79e1c4cf4..3856a06ba2 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -202,7 +202,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; @@ -272,6 +272,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 @@ -299,6 +300,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags) %type key_name +%type any_level %left OR_P %left AND_P @@ -416,14 +418,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 1e82314732..f12f2e750b 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 14deee59d8e56dd0931b7b8708cd8669d496c491 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Mar 2018 22:39:09 +0300 Subject: [PATCH 59/75] 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 99d06e7fe9..e4f7220a8a 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -27,11 +27,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; @@ -693,7 +700,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 }; @@ -785,14 +792,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; @@ -1122,7 +1129,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (whole->type != jbvString) { - if (!cxt->lax) + if (jspStrictAbsenseOfErrors(cxt)) return jperError; error = true; @@ -1132,7 +1139,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, initial->val.string.val, initial->val.string.len)) { - if (cxt->lax) + if (!jspStrictAbsenseOfErrors(cxt)) return jperOk; found = true; @@ -1189,7 +1196,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (str->type != jbvString) { - if (!cxt->lax) + if (jspStrictAbsenseOfErrors(cxt)) return jperError; error = true; @@ -1198,7 +1205,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; @@ -1370,13 +1377,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); @@ -1453,7 +1460,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } } - else + else if (!jspIgnoreStructuralErrors(cxt)) res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; @@ -1493,7 +1500,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)) @@ -1539,8 +1546,10 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, cxt->innermostArraySize = innermostArraySize; } - else + else if (!jspIgnoreStructuralErrors(cxt)) + { res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); + } break; case jpiLast: @@ -1601,7 +1610,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } } - else if (!cxt->lax) + else if (!jspIgnoreStructuralErrors(cxt)) { Assert(found); res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND); @@ -1663,9 +1672,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 }; @@ -1678,6 +1685,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; @@ -1721,9 +1732,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; } @@ -2090,7 +2102,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); @@ -2142,7 +2154,7 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - if (cxt->lax) + if (jspAutoUnwrap(cxt)) { switch (jsp->type) { @@ -2217,11 +2229,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 bba4cf3ba7d4e03e30516e015079a1b587c3b0ac Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Mar 2018 22:39:39 +0300 Subject: [PATCH 60/75] 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 e4f7220a8a..03a89b0406 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1028,7 +1028,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; @@ -1653,7 +1657,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 4d8130a5c2d1a1683a75fc66b59933caf14f1bfb Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 22 Mar 2018 14:25:59 +0300 Subject: [PATCH 61/75] 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 03a89b0406..5fd7c5ed70 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -50,9 +50,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); @@ -524,7 +521,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; @@ -532,9 +529,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) @@ -564,7 +561,7 @@ checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not) &error) == 0; if (error) - return jperError; + return jpbUnknown; break; } @@ -572,16 +569,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; @@ -591,11 +588,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 */ } @@ -624,11 +621,11 @@ makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2) &error); if (error) - return jperError; + return jpbUnknown; } break; default: - return jperError; + return jpbUnknown; } switch (op) @@ -653,10 +650,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 * @@ -743,8 +740,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; @@ -758,12 +755,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))) { @@ -772,35 +769,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; } @@ -808,12 +809,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 @@ -1088,7 +1089,7 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, return jperOk; } -static JsonPathExecResult +static JsonPathBool executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb) { @@ -1106,10 +1107,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); @@ -1117,12 +1118,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))) { @@ -1134,7 +1135,7 @@ executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (whole->type != jbvString) { if (jspStrictAbsenseOfErrors(cxt)) - return jperError; + return jpbUnknown; error = true; } @@ -1144,22 +1145,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) { @@ -1189,7 +1190,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))) { @@ -1201,7 +1202,7 @@ executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp, if (str->type != jbvString) { if (jspStrictAbsenseOfErrors(cxt)) - return jperError; + return jpbUnknown; error = true; } @@ -1210,19 +1211,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; } /* @@ -1256,34 +1257,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 @@ -1296,7 +1403,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; @@ -1305,61 +1412,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, check_stack_depth(); CHECK_FOR_INTERRUPTS(); - 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) { @@ -1620,15 +1695,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: @@ -1641,13 +1707,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; @@ -1677,29 +1747,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: @@ -1893,7 +1940,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jspGetRightArg(jsp, &elem); tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb, - &tzlist, false); + &tzlist); if (jperIsError(tzres)) return tzres; @@ -2046,14 +2093,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); } @@ -2074,7 +2113,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; @@ -2094,7 +2133,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) @@ -2113,7 +2152,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); } /* @@ -2189,40 +2228,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 416e53081f..55e856f9ff 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 b1767c429961a4f0ba26037400c920a79cd321a6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 23 Mar 2018 17:45:59 +0300 Subject: [PATCH 62/75] 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 5fd7c5ed70..eb8f69bd1b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -24,6 +24,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; @@ -755,12 +759,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))) { @@ -906,14 +910,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(); @@ -940,7 +946,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; @@ -1107,7 +1113,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; @@ -1123,7 +1129,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))) { @@ -1190,7 +1196,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))) { @@ -1371,7 +1377,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; } @@ -1380,7 +1386,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; } @@ -2353,59 +2359,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; } @@ -2428,7 +2440,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 55e856f9ff..f1992eba89 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 f12f2e750b..ec49ba7e84 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 c9fe6008654991f6760ad22fd400e58c3a0be02a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 19 Apr 2018 01:53:11 +0300 Subject: [PATCH 63/75] 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 a1f101346bea0f337b43f263c7b40239c403a20b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Sep 2018 16:59:49 +0300 Subject: [PATCH 64/75] Add "id" field to the output of jsonpath .keyvalue() method --- src/backend/utils/adt/jsonpath_exec.c | 124 ++++++++++++++----- src/test/regress/expected/jsonb_jsonpath.out | 30 ++--- 2 files changed, 109 insertions(+), 45 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index eb8f69bd1b..bd6248fbe1 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -27,11 +27,18 @@ /* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */ ErrorData jperNotFound[1]; +typedef struct JsonBaseObjectInfo +{ + JsonbContainer *jbc; + int id; +} JsonBaseObjectInfo; typedef struct JsonPathExecContext { List *vars; JsonbValue *root; /* for $ evaluation */ + JsonBaseObjectInfo baseObject; /* for .keyvalue().id evaluation */ + int generatedObjectId; int innermostArraySize; /* for LAST array index evaluation */ bool laxMode; bool ignoreStructuralErrors; @@ -155,7 +162,7 @@ JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out) /* * Find value of jsonpath variable in a list of passing params */ -static void +static int computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) { ListCell *cell; @@ -164,6 +171,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) Datum computedValue; char *varName; int varNameLength; + int varId = 1; Assert(variable->type == jpiVariable); varName = jspGetString(variable, &varNameLength); @@ -177,6 +185,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) break; var = NULL; + varId++; } if (var == NULL) @@ -190,7 +199,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) if (isNull) { value->type = jbvNull; - return; + return varId; } switch (var->typid) @@ -264,12 +273,14 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("only bool, numeric and text types could be casted to supported jsonpath types"))); } + + return varId; } /* * Convert jsonpath's scalar or variable node to actual jsonb value */ -static void +static int computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *value) { switch(item->type) @@ -290,11 +301,12 @@ computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, JsonbValue *va value->val.string.val = jspGetString(item, &value->val.string.len); break; case jpiVariable: - computeJsonPathVariable(item, cxt->vars, value); - break; + return computeJsonPathVariable(item, cxt->vars, value); default: elog(ERROR, "Wrong type"); } + + return 0; } @@ -1397,6 +1409,44 @@ recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, } } +static inline JsonPathExecResult +recursiveExecuteBase(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jbv, JsonValueList *found) +{ + JsonbValue *v; + JsonbValue vbuf; + bool copy = true; + + if (JsonbType(jbv) == jbvScalar) + { + if (jspHasNext(jsp)) + v = &vbuf; + else + { + v = palloc(sizeof(*v)); + copy = false; + } + + JsonbExtractScalar(jbv->val.binary.data, v); + } + else + v = jbv; + + return recursiveExecuteNext(cxt, jsp, NULL, v, found, copy); +} + +static inline JsonBaseObjectInfo +setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id) +{ + JsonBaseObjectInfo baseObject = cxt->baseObject; + + cxt->baseObject.jbc = jbv->type != jbvBinary ? NULL : + (JsonbContainer *) jbv->val.binary.data; + cxt->baseObject.id = id; + + return baseObject; +} + /* * Main executor function: walks on jsonpath structure and tries to find * correspoding parts of jsonb. Note, jsonb and jsonpath values should be @@ -1414,6 +1464,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathItem elem; JsonPathExecResult res = jperNotFound; bool hasNext; + JsonBaseObjectInfo baseObject; check_stack_depth(); CHECK_FOR_INTERRUPTS(); @@ -1474,33 +1525,18 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND); } break; + case jpiRoot: jb = cxt->root; - /* fall through */ - case jpiCurrent: - { - JsonbValue *v; - JsonbValue vbuf; - bool copy = true; - - if (JsonbType(jb) == jbvScalar) - { - if (jspHasNext(jsp)) - v = &vbuf; - else - { - v = palloc(sizeof(*v)); - copy = false; - } + baseObject = setBaseObject(cxt, jb, 0); + res = recursiveExecuteBase(cxt, jsp, jb, found); + cxt->baseObject = baseObject; + break; - JsonbExtractScalar(jb->val.binary.data, v); - } - else - v = jb; + case jpiCurrent: + res = recursiveExecuteBase(cxt, jsp, jb, found); + break; - res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy); - break; - } case jpiAnyArray: if (JsonbType(jb) == jbvArray) { @@ -1762,6 +1798,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue vbuf; JsonbValue *v; bool hasNext = jspGetNext(jsp, &elem); + int id; if (!hasNext && !found) { @@ -1771,9 +1808,11 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, v = hasNext ? &vbuf : palloc(sizeof(*v)); - computeJsonPathItem(cxt, jsp, v); + id = computeJsonPathItem(cxt, jsp, v); + baseObject = setBaseObject(cxt, v, id); res = recursiveExecuteNext(cxt, jsp, &elem, v, found, hasNext); + cxt->baseObject = baseObject; } break; case jpiType: @@ -2026,11 +2065,14 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue bin; JsonbValue key; JsonbValue val; + JsonbValue idval; JsonbValue obj; JsonbValue keystr; JsonbValue valstr; + JsonbValue idstr; JsonbIterator *it; JsonbParseState *ps = NULL; + int64 id; hasNext = jspGetNext(jsp, &elem); @@ -2053,9 +2095,21 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, valstr.val.string.val = "value"; valstr.val.string.len = 5; + idstr.type = jbvString; + idstr.val.string.val = "id"; + idstr.val.string.len = 2; + if (jb->type == jbvObject) jb = JsonbWrapInBinary(jb, &bin); + id = jb->type != jbvBinary ? 0 : + (int64)((char *) jb->val.binary.data - + (char *) cxt->baseObject.jbc); + id += (int64) cxt->baseObject.id * INT64CONST(10000000000); + + idval.type = jbvNumeric; + idval.val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, Int64GetDatum(id))); + it = JsonbIteratorInit(jb->val.binary.data); while ((r = JsonbIteratorNext(&it, &key, true)) != WJB_DONE) @@ -2078,18 +2132,25 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, pushJsonbValue(&ps, WJB_KEY, &keystr); pushJsonbValue(&ps, WJB_VALUE, &key); - pushJsonbValue(&ps, WJB_KEY, &valstr); pushJsonbValue(&ps, WJB_VALUE, &val); + pushJsonbValue(&ps, WJB_KEY, &idstr); + pushJsonbValue(&ps, WJB_VALUE, &idval); + keyval = pushJsonbValue(&ps, WJB_END_OBJECT, NULL); jsonb = JsonbValueToJsonb(keyval); JsonbInitBinary(&obj, jsonb); + baseObject = setBaseObject(cxt, &obj, + cxt->generatedObjectId++); + res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true); + cxt->baseObject = baseObject; + if (jperIsError(res)) break; @@ -2254,6 +2315,9 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJso cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = JsonbInitBinary(&jbv, json); + cxt.baseObject.jbc = NULL; + cxt.baseObject.id = 0; + cxt.generatedObjectId = list_length(vars) + 1; cxt.innermostArraySize = -1; if (jspStrictAbsenseOfErrors(&cxt) && !foundJson) diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index ec49ba7e84..7e9cdd41cd 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -1060,29 +1060,29 @@ select jsonb '{}' @* '$.keyvalue()'; (0 rows) 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"}} + ?column? +---------------------------------------------- + {"id": 0, "key": "a", "value": 1} + {"id": 0, "key": "b", "value": [1, 2]} + {"id": 0, "key": "c", "value": {"a": "bbb"}} (3 rows) 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"}} + ?column? +----------------------------------------------- + {"id": 12, "key": "a", "value": 1} + {"id": 12, "key": "b", "value": [1, 2]} + {"id": 72, "key": "c", "value": {"a": "bbb"}} (3 rows) select jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()'; ERROR: SQL/JSON object not found 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"}} + ?column? +----------------------------------------------- + {"id": 12, "key": "a", "value": 1} + {"id": 12, "key": "b", "value": [1, 2]} + {"id": 72, "key": "c", "value": {"a": "bbb"}} (3 rows) select jsonb 'null' @* '$.double()'; From 0a15a393523aa9b9003006c93391a09d87d8626c Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 6 Sep 2018 17:02:47 +0300 Subject: [PATCH 65/75] Remove auto-wrapping in jsonpath recursiveExecute() --- src/backend/utils/adt/jsonpath_exec.c | 43 +++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index bd6248fbe1..a9ad32817e 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1581,17 +1581,23 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, } } } + else if (jspAutoWrap(cxt)) + res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true); else if (!jspIgnoreStructuralErrors(cxt)) res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND); break; case jpiIndexArray: - if (JsonbType(jb) == jbvArray) + if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt)) { int innermostArraySize = cxt->innermostArraySize; int i; int size = JsonbArraySize(jb); bool binary = jb->type == jbvBinary; + bool singleton = size < 0; + + if (singleton) + size = 1; cxt->innermostArraySize = size; /* for LAST evaluation */ @@ -1640,16 +1646,32 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, for (index = index_from; index <= index_to; index++) { - JsonbValue *v = binary ? - getIthJsonbValueFromContainer(jb->val.binary.data, - (uint32) index) : - &jb->val.array.elems[index]; + JsonbValue *v; + bool copy; + + if (singleton) + { + v = jb; + copy = true; + } + else if (binary) + { + v = getIthJsonbValueFromContainer(jb->val.binary.data, + (uint32) index); - if (v == NULL) - continue; + if (v == NULL) + continue; + + copy = false; + } + else + { + v = &jb->val.array.elems[index]; + copy = true; + } res = recursiveExecuteNext(cxt, jsp, &elem, v, found, - !binary); + copy); if (jperIsError(res)) break; @@ -2285,11 +2307,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, case jpiKeyValue: return recursiveExecuteUnwrap(cxt, jsp, jb, found); - case jpiAnyArray: - case jpiIndexArray: - jb = wrapItem(jb); - break; - default: break; } From e4905c98db6c26171d358aa3bfb27ff32484f85f Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 18 May 2017 17:09:44 +0300 Subject: [PATCH 66/75] Switch to relative jsonpath item offsets --- src/backend/utils/adt/jsonpath.c | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index b7be28675e..536e4899d9 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -92,17 +92,16 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, right = buf->len; appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right)); - chld = !item->value.args.left ? 0 : + chld = !item->value.args.left ? pos : flattenJsonPathParseItem(buf, item->value.args.left, allowCurrent, insideArraySubscript); - *(int32*)(buf->data + left) = chld; - - chld = !item->value.args.right ? 0 : + *(int32*)(buf->data + left) = chld - pos; + chld = !item->value.args.right ? pos : flattenJsonPathParseItem(buf, item->value.args.right, allowCurrent, insideArraySubscript); - *(int32*)(buf->data + right) = chld; + *(int32*)(buf->data + right) = chld - pos; } break; case jpiLikeRegex: @@ -125,7 +124,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, allowCurrent, insideArraySubscript); - *(int32 *)(buf->data + offs) = chld; + *(int32 *)(buf->data + offs) = chld - pos; } break; case jpiFilter: @@ -144,7 +143,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, item->type == jpiFilter || allowCurrent, insideArraySubscript); - *(int32*)(buf->data + arg) = chld; + *(int32*)(buf->data + arg) = chld - pos; } break; case jpiNull: @@ -185,12 +184,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 frompos = flattenJsonPathParseItem(buf, item->value.array.elems[i].from, - true, true); + true, true) - pos; if (item->value.array.elems[i].to) topos = flattenJsonPathParseItem(buf, item->value.array.elems[i].to, - true, true); + true, true) - pos; else topos = 0; @@ -224,7 +223,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { chld = flattenJsonPathParseItem(buf, item->next, allowCurrent, - insideArraySubscript); + insideArraySubscript) - pos; *(int32 *)(buf->data + next) = chld; } @@ -626,18 +625,10 @@ jspInit(JsonPathItem *v, JsonPath *js) void jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) { - v->base = base; + v->base = base + pos; read_byte(v->type, base, pos); - - switch(INTALIGN(pos) - pos) - { - case 3: pos++; - case 2: pos++; - case 1: pos++; - default: break; - } - + pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base; read_int32(v->nextPos, base, pos); switch(v->type) From 670aa7776ac0d42946d86ff14d60200c19719b5b Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 7 Sep 2018 15:00:27 +0300 Subject: [PATCH 67/75] Fix current item references in jsonpath array subscripts --- src/backend/utils/adt/jsonpath.c | 24 ++++---- src/backend/utils/adt/jsonpath_exec.c | 63 +++++++++++++++++++- src/test/regress/expected/jsonb_jsonpath.out | 2 +- src/test/regress/expected/jsonpath.out | 12 ++-- src/test/regress/sql/jsonb_jsonpath.sql | 2 +- src/test/regress/sql/jsonpath.sql | 4 +- 6 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index 536e4899d9..11d457d505 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -25,12 +25,13 @@ */ static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, - bool allowCurrent, bool insideArraySubscript) + int nestingLevel, bool insideArraySubscript) { /* position from begining of jsonpath data */ int32 pos = buf->len - JSONPATH_HDRSZ; int32 chld; int32 next; + int argNestingLevel = 0; check_stack_depth(); CHECK_FOR_INTERRUPTS(); @@ -94,12 +95,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, chld = !item->value.args.left ? pos : flattenJsonPathParseItem(buf, item->value.args.left, - allowCurrent, + nestingLevel + argNestingLevel, insideArraySubscript); *(int32*)(buf->data + left) = chld - pos; chld = !item->value.args.right ? pos : flattenJsonPathParseItem(buf, item->value.args.right, - allowCurrent, + nestingLevel + argNestingLevel, insideArraySubscript); *(int32*)(buf->data + right) = chld - pos; } @@ -122,12 +123,14 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, appendStringInfoChar(buf, '\0'); chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr, - allowCurrent, + nestingLevel, insideArraySubscript); *(int32 *)(buf->data + offs) = chld - pos; } break; case jpiFilter: + argNestingLevel++; + /* fall through */ case jpiIsUnknown: case jpiNot: case jpiPlus: @@ -140,8 +143,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg)); chld = flattenJsonPathParseItem(buf, item->value.arg, - item->type == jpiFilter || - allowCurrent, + nestingLevel + argNestingLevel, insideArraySubscript); *(int32*)(buf->data + arg) = chld - pos; } @@ -154,7 +156,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, case jpiAnyKey: break; case jpiCurrent: - if (!allowCurrent) + if (nestingLevel <= 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("@ is not allowed in root expressions"))); @@ -184,12 +186,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, int32 frompos = flattenJsonPathParseItem(buf, item->value.array.elems[i].from, - true, true) - pos; + nestingLevel, true) - pos; if (item->value.array.elems[i].to) topos = flattenJsonPathParseItem(buf, item->value.array.elems[i].to, - true, true) - pos; + nestingLevel, true) - pos; else topos = 0; @@ -222,7 +224,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item, if (item->next) { - chld = flattenJsonPathParseItem(buf, item->next, allowCurrent, + chld = flattenJsonPathParseItem(buf, item->next, nestingLevel, insideArraySubscript) - pos; *(int32 *)(buf->data + next) = chld; } @@ -249,7 +251,7 @@ jsonpath_in(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid input syntax for jsonpath: \"%s\"", in))); - flattenJsonPathParseItem(&buf, jsonpath->expr, false, false); + flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false); res = (JsonPath*)buf.data; SET_VARSIZE(res, buf.len); diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index a9ad32817e..ed5a87d984 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -33,10 +33,19 @@ typedef struct JsonBaseObjectInfo int id; } JsonBaseObjectInfo; +typedef struct JsonItemStackEntry +{ + JsonbValue *item; + struct JsonItemStackEntry *parent; +} JsonItemStackEntry; + +typedef JsonItemStackEntry *JsonItemStack; + typedef struct JsonPathExecContext { List *vars; JsonbValue *root; /* for $ evaluation */ + JsonItemStack stack; /* for @N evaluation */ JsonBaseObjectInfo baseObject; /* for .keyvalue().id evaluation */ int generatedObjectId; int innermostArraySize; /* for LAST array index evaluation */ @@ -61,6 +70,10 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); +static inline JsonPathExecResult recursiveExecuteNested(JsonPathExecContext *cxt, + JsonPathItem *jsp, JsonbValue *jb, + JsonValueList *found); + static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found); @@ -157,6 +170,20 @@ JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out) return JsonbInitBinary(out, jb); } +static inline void +pushJsonItem(JsonItemStack *stack, JsonItemStackEntry *entry, JsonbValue *item) +{ + entry->item = item; + entry->parent = *stack; + *stack = entry; +} + +static inline void +popJsonItem(JsonItemStack *stack) +{ + *stack = (*stack)->parent; +} + /********************Execute functions for JsonPath***************************/ /* @@ -1409,6 +1436,34 @@ recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp, } } +static inline JsonPathExecResult +recursiveExecuteNested(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb, JsonValueList *found) +{ + JsonItemStackEntry current; + JsonPathExecResult res; + + pushJsonItem(&cxt->stack, ¤t, jb); + res = recursiveExecute(cxt, jsp, jb, found); + popJsonItem(&cxt->stack); + + return res; +} + +static inline JsonPathBool +recursiveExecuteBoolNested(JsonPathExecContext *cxt, JsonPathItem *jsp, + JsonbValue *jb) +{ + JsonItemStackEntry current; + JsonPathBool res; + + pushJsonItem(&cxt->stack, ¤t, jb); + res = recursiveExecuteBool(cxt, jsp, jb, false); + popJsonItem(&cxt->stack); + + return res; +} + static inline JsonPathExecResult recursiveExecuteBase(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jbv, JsonValueList *found) @@ -1534,7 +1589,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiCurrent: - res = recursiveExecuteBase(cxt, jsp, jb, found); + res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found); break; case jpiAnyArray: @@ -1775,7 +1830,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonPathBool st; jspGetArg(jsp, &elem); - st = recursiveExecuteBool(cxt, &elem, jb, false); + st = recursiveExecuteBoolNested(cxt, &elem, jb); if (st != jpbTrue) res = jperNotFound; else @@ -2325,6 +2380,7 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJso JsonPathExecContext cxt; JsonPathItem jsp; JsonbValue jbv; + JsonItemStackEntry root; jspInit(&jsp, path); @@ -2332,11 +2388,14 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJso cxt.laxMode = (path->header & JSONPATH_LAX) != 0; cxt.ignoreStructuralErrors = cxt.laxMode; cxt.root = JsonbInitBinary(&jbv, json); + cxt.stack = NULL; cxt.baseObject.jbc = NULL; cxt.baseObject.id = 0; cxt.generatedObjectId = list_length(vars) + 1; cxt.innermostArraySize = -1; + pushJsonItem(&cxt.stack, &root, cxt.root); + if (jspStrictAbsenseOfErrors(&cxt) && !foundJson) { /* diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out index 7e9cdd41cd..f93c930473 100644 --- a/src/test/regress/expected/jsonb_jsonpath.out +++ b/src/test/regress/expected/jsonb_jsonpath.out @@ -234,7 +234,7 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a'; 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} diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out index 8cd0534195..193fc6841a 100644 --- a/src/test/regress/expected/jsonpath.out +++ b/src/test/regress/expected/jsonpath.out @@ -303,10 +303,10 @@ select '$.a[1,2, 3 to 16]'::jsonpath; $."a"[1,2,3 to 16] (1 row) -select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath; +select '$.a[$a + 1, ($b[*]) to -($[0] * 2)]'::jsonpath; jsonpath ---------------------------------------- - $."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)] + $."a"[$"a" + 1,$"b"[*] to -($[0] * 2)] (1 row) select '$.a[$.a.size() - 3]'::jsonpath; @@ -341,10 +341,10 @@ select '$[last]'::jsonpath; $[last] (1 row) -select '$[@ ? (last > 0)]'::jsonpath; - jsonpath ------------------ - $[@?(last > 0)] +select '$[$[0] ? (last > 0)]'::jsonpath; + jsonpath +-------------------- + $[$[0]?(last > 0)] (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 369463d844..43f34ef5d3 100644 --- a/src/test/regress/sql/jsonb_jsonpath.sql +++ b/src/test/regress/sql/jsonb_jsonpath.sql @@ -39,7 +39,7 @@ 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}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]'; select jsonb '1' @* 'lax $[0]'; select jsonb '1' @* 'lax $[*]'; select jsonb '[1]' @* 'lax $[0]'; diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql index 7b546873dd..8a3ea423b8 100644 --- a/src/test/regress/sql/jsonpath.sql +++ b/src/test/regress/sql/jsonpath.sql @@ -54,14 +54,14 @@ select '$a.b'::jsonpath; select '$a[*]'::jsonpath; select '$.g ? (@.zip == $zip)'::jsonpath; select '$.a[1,2, 3 to 16]'::jsonpath; -select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::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; select '$ ? (last > 0)'::jsonpath; select '$[last]'::jsonpath; -select '$[@ ? (last > 0)]'::jsonpath; +select '$[$[0] ? (last > 0)]'::jsonpath; select 'null.type()'::jsonpath; select '1.type()'::jsonpath; From 372dd39c624aa5bb64d3d0659e3aa46ad74a08ea Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Fri, 29 Dec 2017 20:57:08 +0300 Subject: [PATCH 68/75] 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 ed5a87d984..b15ac6ba40 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -121,6 +121,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) { @@ -149,6 +152,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) { @@ -159,6 +165,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) { @@ -366,6 +376,9 @@ JsonbType(JsonbValue *jb) return type; } +/* + * Get the type name of a SQL/JSON item. + */ static const char * JsonbTypeName(JsonbValue *jb) { @@ -423,6 +436,9 @@ JsonbTypeName(JsonbValue *jb) } } +/* + * Returns the size of an array item, or -1 if item is not an array. + */ static int JsonbArraySize(JsonbValue *jb) { @@ -440,6 +456,9 @@ JsonbArraySize(JsonbValue *jb) return -1; } +/* + * Compare two numerics. + */ static int compareNumeric(Numeric a, Numeric b) { @@ -452,6 +471,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) { @@ -564,6 +587,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) { @@ -621,6 +647,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) { @@ -709,6 +738,9 @@ copyJsonbValue(JsonbValue *src) return dst; } +/* + * Execute next jsonpath item if it does exist. + */ static inline JsonPathExecResult recursiveExecuteNext(JsonPathExecContext *cxt, JsonPathItem *cur, JsonPathItem *next, @@ -736,6 +768,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) @@ -783,6 +819,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) { @@ -860,6 +902,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) @@ -969,6 +1015,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) @@ -1103,6 +1153,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) @@ -2244,6 +2298,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) @@ -2289,6 +2346,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) @@ -2341,6 +2401,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 8a115e7c0921025f83e5d0872aaf63a76d07872a Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 1 Nov 2018 02:42:02 +0300 Subject: [PATCH 69/75] Add comments for JsonPathItemType --- src/include/utils/jsonpath.h | 86 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h index f1992eba89..5d16131de9 100644 --- a/src/include/utils/jsonpath.h +++ b/src/include/utils/jsonpath.h @@ -39,49 +39,49 @@ typedef struct * All node's type of jsonpath expression */ typedef enum JsonPathItemType { - jpiNull = jbvNull, - jpiString = jbvString, - jpiNumeric = jbvNumeric, - jpiBool = jbvBool, - jpiAnd, - jpiOr, - jpiNot, - jpiIsUnknown, - jpiEqual, - jpiNotEqual, - jpiLess, - jpiGreater, - jpiLessOrEqual, - jpiGreaterOrEqual, - jpiAdd, - jpiSub, - jpiMul, - jpiDiv, - jpiMod, - jpiPlus, - jpiMinus, - jpiAnyArray, - jpiAnyKey, - jpiIndexArray, - jpiAny, - jpiKey, - jpiCurrent, - jpiRoot, - jpiVariable, - jpiFilter, - jpiExists, - jpiType, - jpiSize, - jpiAbs, - jpiFloor, - jpiCeiling, - jpiDouble, - jpiDatetime, - jpiKeyValue, - jpiSubscript, - jpiLast, - jpiStartsWith, - jpiLikeRegex, + jpiNull = jbvNull, /* NULL literal */ + jpiString = jbvString, /* string literal */ + jpiNumeric = jbvNumeric, /* numeric literal */ + jpiBool = jbvBool, /* boolean literal: TRUE or FALSE */ + jpiAnd, /* predicate && predicate */ + jpiOr, /* predicate || predicate */ + jpiNot, /* ! predicate */ + jpiIsUnknown, /* (predicate) IS UNKNOWN */ + jpiEqual, /* expr == expr */ + jpiNotEqual, /* expr != expr */ + jpiLess, /* expr < expr */ + jpiGreater, /* expr > expr */ + jpiLessOrEqual, /* expr <= expr */ + jpiGreaterOrEqual, /* expr >= expr */ + jpiAdd, /* expr + expr */ + jpiSub, /* expr - expr */ + jpiMul, /* expr * expr */ + jpiDiv, /* expr / expr */ + jpiMod, /* expr % expr */ + jpiPlus, /* + expr */ + jpiMinus, /* - expr */ + jpiAnyArray, /* [*] */ + jpiAnyKey, /* .* */ + jpiIndexArray, /* [subscript, ...] */ + jpiAny, /* .** */ + jpiKey, /* .key */ + jpiCurrent, /* @ */ + jpiRoot, /* $ */ + jpiVariable, /* $variable */ + jpiFilter, /* ? (predicate) */ + jpiExists, /* EXISTS (expr) predicate */ + jpiType, /* .type() item method */ + jpiSize, /* .size() item method */ + jpiAbs, /* .abs() item method */ + jpiFloor, /* .floor() item method */ + jpiCeiling, /* .ceiling() item method */ + jpiDouble, /* .double() item method */ + jpiDatetime, /* .datetime() item method */ + jpiKeyValue, /* .keyvalue() item method */ + jpiSubscript, /* array subscript: 'expr' or 'expr TO expr' */ + jpiLast, /* LAST array subscript */ + jpiStartsWith, /* STARTS WITH predicate */ + jpiLikeRegex, /* LIKE_REGEX predicate */ } JsonPathItemType; /* XQuery regex mode flags for LIKE_REGEX predicate */ From 47ef0ad01ce915a5ecf47e17be66c73d2daf7a81 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 16 Aug 2017 15:54:51 +0300 Subject: [PATCH 70/75] 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 | 25 +- 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 | 106 ++ 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, 3160 insertions(+), 35 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 d335eec09a..8db7f98cf6 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 name.o \ network.o network_gist.o network_selfuncs.o network_spgist.o \ numeric.o numutils.o oid.o oracle_compat.o \ @@ -42,6 +42,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 f4dda6b9e2..428167ef8a 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 5e600e0b09..8a45cb05ff 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 b15ac6ba40..ea6372026b 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -24,8 +24,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 JsonBaseObjectInfo { @@ -152,6 +158,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it) return lfirst(it->lcell); } +#ifndef JSONPATH_JSON_C /* * Initialize a binary JsonbValue with the given jsonb container. */ @@ -164,6 +171,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb) return jbv; } +#endif /* * Transform a JsonbValue into a binary JsonbValue by encoding it to a @@ -292,7 +300,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); @@ -361,7 +369,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; @@ -386,7 +394,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); @@ -447,7 +455,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); @@ -2234,8 +2242,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp, jb = JsonbWrapInBinary(jb, &bin); id = jb->type != jbvBinary ? 0 : +#ifdef JSONPATH_JSON_C + (int64)((char *)((JsonContainer *) jb->val.binary.data)->data - + (char *) cxt->baseObject.jbc->data); +#else (int64)((char *) jb->val.binary.data - - (char *) cxt->baseObject.jbc); + (char *) cxt->baseObject.jbc); +#endif id += (int64) cxt->baseObject.id * INT64CONST(10000000000); idval.type = jbvNumeric; @@ -2545,7 +2558,7 @@ makePassingVars(Jsonb *jb) datumCopy(NumericGetDatum(v.val.numeric), false, -1)); 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 a18eefd2aa..e08057a6b2 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -3209,5 +3209,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 31ddc30611..74f49e5e59 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -9139,6 +9139,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 9c47f1fc12..22643dece6 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -221,10 +221,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 @@ -278,6 +278,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 */ @@ -373,6 +375,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, @@ -381,6 +385,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..064d77ef6b --- /dev/null +++ b/src/include/utils/jsonpath_json.h @@ -0,0 +1,106 @@ +/*------------------------------------------------------------------------- + * + * jsonpath_json.h + * Jsonpath support for json datatype + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/utils/jsonpath_json.h + * + *------------------------------------------------------------------------- + */ + +#ifndef JSONPATH_JSON_H +#define JSONPATH_JSON_H + +/* redefine jsonb structures */ +#define Jsonb Json +#define JsonbContainer JsonContainer +#define JsonbIterator JsonIterator + +/* redefine jsonb functions */ +#define findJsonbValueFromContainer(jc, flags, jbv) \ + findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv) +#define getIthJsonbValueFromContainer(jc, i) \ + getIthJsonValueFromContainer((JsonContainer *)(jc), i) +#define pushJsonbValue pushJsonValue +#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc)) +#define JsonbIteratorNext JsonIteratorNext +#define JsonbValueToJsonb JsonbValueToJson +#define JsonbToCString JsonToCString +#define JsonbUnquote JsonUnquote +#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv) + +/* redefine jsonb macros */ +#undef JsonContainerSize +#define JsonContainerSize(jc) \ + ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \ + JsonContainerIsArray(jc) \ + ? JsonGetArraySize((JsonContainer *)(jc)) \ + : ((JsonContainer *)(jc))->header & JB_CMASK) + + +#undef DatumGetJsonbP +#define DatumGetJsonbP(d) DatumGetJsonP(d) + +#undef DatumGetJsonbPCopy +#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d) + +#undef JsonbPGetDatum +#define JsonbPGetDatum(json) JsonPGetDatum(json) + +#undef PG_GETARG_JSONB_P +#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n)) + +#undef PG_GETARG_JSONB_P_COPY +#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n)) + +#undef PG_RETURN_JSONB_P +#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json)) + + +#ifdef DatumGetJsonb +#undef DatumGetJsonb +#define DatumGetJsonb(d) DatumGetJsonbP(d) +#endif + +#ifdef DatumGetJsonbCopy +#undef DatumGetJsonbCopy +#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d) +#endif + +#ifdef JsonbGetDatum +#undef JsonbGetDatum +#define JsonbGetDatum(json) JsonbPGetDatum(json) +#endif + +#ifdef PG_GETARG_JSONB +#undef PG_GETARG_JSONB +#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n) +#endif + +#ifdef PG_GETARG_JSONB_COPY +#undef PG_GETARG_JSONB_COPY +#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n) +#endif + +#ifdef PG_RETURN_JSONB +#undef PG_RETURN_JSONB +#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json) +#endif + +/* redefine global jsonpath functions */ +#define executeJsonPath executeJsonPathJson + +static inline JsonbValue * +JsonbInitBinary(JsonbValue *jbv, Json *jb) +{ + jbv->type = jbvBinary; + jbv->val.binary.data = (void *) &jb->root; + jbv->val.binary.len = jb->root.len; + + return jbv; +} + +#endif /* JSONPATH_JSON_H */ diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out new file mode 100644 index 0000000000..83498e1ec4 --- /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 jsonpath variable 'value' +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, "id": 0} + {"key": "b", "value": [1, 2], "id": 0} + {"key": "c", "value": {"a": "bbb"}, "id": 0} +(3 rows) + +select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()'; + ?column? +----------------------------------------------- + {"key": "a", "value": 1, "id": 1} + {"key": "b", "value": [1, 2], "id": 1} + {"key": "c", "value": {"a": "bbb"}, "id": 24} +(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, "id": 1} + {"key": "b", "value": [1, 2], "id": 1} + {"key": "c", "value": {"a": "bbb"}, "id": 24} +(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 4b4fc93c3a..05c7b2d31f 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 64b07fc78e..b5d1505a14 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -157,6 +157,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..a12db18811 --- /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 50e76ae013476e3531a19dd77664a205e72f4568 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 21 Feb 2018 03:24:39 +0300 Subject: [PATCH 71/75] 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 428167ef8a..fb61d684bc 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 83498e1ec4..7c86de2aec 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 a12db18811..b2baa9ebb0 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 9d8a8dbfb012eb3580ce51634bda0a8432f61e24 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Thu, 8 Mar 2018 01:38:40 +0300 Subject: [PATCH 72/75] 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 7c86de2aec..942bf6bd99 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 b2baa9ebb0..824f510199 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 4e33fe47cde63a52cd25b1b8e2f5003721916ec6 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Tue, 27 Mar 2018 16:24:57 +0300 Subject: [PATCH 73/75] 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 | 108 ++++------ src/backend/utils/adt/numeric.c | 294 +++++++++++++++++--------- src/include/utils/elog.h | 19 ++ src/include/utils/float.h | 7 +- src/include/utils/numeric.h | 9 + 6 files changed, 297 insertions(+), 188 deletions(-) diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c index c91bb1a305..391b323acf 100644 --- a/src/backend/utils/adt/float.c +++ b/src/backend/utils/adt/float.c @@ -287,7 +287,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 @@ -305,8 +305,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; @@ -320,10 +320,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); @@ -396,17 +399,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 @@ -429,10 +436,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 ea6372026b..4a7c9a3c74 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -19,6 +19,7 @@ #include "regex/regex.h" #include "utils/builtins.h" #include "utils/datum.h" +#include "utils/float.h" #include "utils/formatting.h" #include "utils/json.h" #include "utils/jsonpath.h" @@ -918,7 +919,6 @@ static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found) { - MemoryContext mcxt = CurrentMemoryContext; JsonPathExecResult jper; JsonPathItem elem; JsonValueList lseq = { 0 }; @@ -927,11 +927,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); @@ -970,25 +969,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); @@ -996,29 +992,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); } @@ -2033,53 +2015,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 444e575e1d..8893878e26 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -466,14 +466,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); @@ -510,12 +511,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); @@ -616,7 +617,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 @@ -1579,14 +1580,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, @@ -2386,17 +2387,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; @@ -2406,7 +2399,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. @@ -2417,24 +2410,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; @@ -2444,7 +2444,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. @@ -2455,24 +2455,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; @@ -2482,7 +2489,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. @@ -2497,24 +2504,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; @@ -2525,7 +2539,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 @@ -2543,12 +2557,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); } @@ -2585,7 +2617,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); @@ -2594,36 +2626,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); } @@ -3227,55 +3266,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); } @@ -3319,7 +3376,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); @@ -4894,7 +4951,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 */ @@ -5620,7 +5677,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; @@ -5658,10 +5716,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); @@ -5682,10 +5743,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++; } @@ -5706,10 +5770,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; /* @@ -5721,9 +5789,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) @@ -6065,7 +6137,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); @@ -6087,15 +6159,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; @@ -6166,14 +6237,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() - @@ -7051,7 +7130,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; @@ -7076,9 +7155,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 @@ -7699,7 +7781,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; @@ -7711,7 +7794,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); @@ -8364,7 +8450,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/elog.h b/src/include/utils/elog.h index 33c6b53e27..42a834c241 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -143,6 +143,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/float.h b/src/include/utils/float.h index 05e1b27637..d082bdcdd3 100644 --- a/src/include/utils/float.h +++ b/src/include/utils/float.h @@ -38,8 +38,11 @@ extern PGDLLIMPORT int extra_float_digits; * Utility functions in float.c */ extern int is_infinite(float8 val); -extern float8 float8in_internal(char *num, char **endptr_p, - const char *type_name, const char *orig_string); +extern float8 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(float8 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/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 a00c915dd3fa833a5bce45a58fd9ce9debee1af0 Mon Sep 17 00:00:00 2001 From: Nikita Glukhov Date: Wed, 24 May 2017 01:59:35 +0300 Subject: [PATCH 74/75] 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 075a54c4ac..b2d226f475 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1433,11 +1433,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 22643dece6..404ed70169 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 5d16131de9..b3cf4c2657 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 f045e08538..92cf4c79c7 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -2718,6 +2718,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}'; @@ -2793,6 +2901,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'; @@ -2943,6 +3241,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 c073a5ac3f..dd7b142c18 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1845,6 +1845,8 @@ ORDER BY 1, 2, 3; 2742 | 9 | ? 2742 | 10 | ?| 2742 | 11 | ?& + 2742 | 15 | @? + 2742 | 16 | @~ 3580 | 1 | < 3580 | 1 | << 3580 | 2 | &< @@ -1910,7 +1912,7 @@ ORDER BY 1, 2, 3; 4000 | 26 | >> 4000 | 27 | >>= 4000 | 28 | ^@ -(123 rows) +(125 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 bd82fd13f7..1430a98ac3 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -735,6 +735,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; @@ -753,6 +771,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'; @@ -802,6 +853,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 5271a250afa333f0712d755ab787261519cdadcb Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Fri, 1 Jun 2018 02:02:30 -0400 Subject: [PATCH 75/75] Add CI control files for Travis and AppVeyor. This commit is not intended to be part of a patch submission, it's just a way to trigger builds on Travis and/or AppVeyor. Push a PostgreSQL source tree to GitHub including this commit, after enabling travis-ci.org and/or appveyor.com to watch your GitHub repo and build any branch containing .travis.yml/appveyor.yml. This is a good way to find out what will happen on cfbot.cputube.org after you post a patch to the pgsql-hackers mailing list in a thread that is registered in commitfest.postgresql.org. Add described at https://wiki.postgresql.org/wiki/Continuous_Integration . --- .travis.yml | 33 +++++++++++++++++++++++++++++++++ appveyor.yml | 25 +++++++++++++++++++++++++ buildsetup.pl | 38 ++++++++++++++++++++++++++++++++++++++ dumpregr.pl | 20 ++++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 .travis.yml create mode 100644 appveyor.yml create mode 100644 buildsetup.pl create mode 100644 dumpregr.pl diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..c5e05f5950 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +sudo: required +addons: + apt: + packages: + - gdb + - lcov + - libipc-run-perl + - libperl-dev + - libpython-dev + - tcl-dev + - libldap2-dev + - libicu-dev + - docbook + - docbook-dsssl + - docbook-xsl + - libxml2-utils + - openjade1.3 + - opensp + - xsltproc +language: c +cache: ccache +before_install: + - echo '/tmp/%e-%s-%p.core' | sudo tee /proc/sys/kernel/core_pattern +script: ./configure --enable-debug --enable-cassert --enable-tap-tests --with-tcl --with-python --with-perl --with-ldap --with-icu && make -j4 all contrib docs && make check-world +after_failure: + - for f in $(find . -name regression.diffs) ; do echo "========= Contents of $f" ; head -1000 $f ; done + - | + for corefile in $(find /tmp/ -name '*.core' 2>/dev/null) ; do + binary=$(gdb -quiet -core $corefile -batch -ex 'info auxv' | grep AT_EXECFN | perl -pe "s/^.*\"(.*)\"\$/\$1/g") + echo dumping $corefile for $binary + gdb --batch --quiet -ex "thread apply all bt full" -ex "quit" $binary $corefile + done + diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..04e041ad63 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,25 @@ +# appveyor.yml +install: + - cinst winflexbison + - '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64' + +before_build: + - rename c:\ProgramData\chocolatey\bin\win_flex.exe flex.exe + - rename c:\ProgramData\chocolatey\bin\win_bison.exe bison.exe + - perl buildsetup.pl + +build: + project: pgsql.sln + +before_test: + - 'perl -p -i.bak -e "s/^test: tablespace/#test: tablespace/" src/test/regress/serial_schedule' + - 'perl -p -i.bak -e "s/^test: tablespace/#test: tablespace/" src/test/regress/parallel_schedule' + +test_script: + - cd src\tools\msvc && vcregress check + +on_failure: + - perl dumpregr.pl + +configuration: + - Release diff --git a/buildsetup.pl b/buildsetup.pl new file mode 100644 index 0000000000..23df2fb1aa --- /dev/null +++ b/buildsetup.pl @@ -0,0 +1,38 @@ +# first part of postgres build.pl, just doesn't run msbuild + +use strict; + +BEGIN +{ + + chdir("../../..") if (-d "../msvc" && -d "../../../src"); + +} + +use lib "src/tools/msvc"; + +use Cwd; + +use Mkvcbuild; + +# buildenv.pl is for specifying the build environment settings +# it should contain lines like: +# $ENV{PATH} = "c:/path/to/bison/bin;$ENV{PATH}"; + +if (-e "src/tools/msvc/buildenv.pl") +{ + do "src/tools/msvc/buildenv.pl"; +} +elsif (-e "./buildenv.pl") +{ + do "./buildenv.pl"; +} + +# set up the project +our $config; +do "config_default.pl"; +do "config.pl" if (-f "src/tools/msvc/config.pl"); + +# print "PATH: $_\n" foreach (split(';',$ENV{PATH})); + +Mkvcbuild::mkvcbuild($config); diff --git a/dumpregr.pl b/dumpregr.pl new file mode 100644 index 0000000000..08d276b52d --- /dev/null +++ b/dumpregr.pl @@ -0,0 +1,20 @@ +use strict; +use warnings FATAL => qw(all); + +use File::Find; + +my $Target = "regression.diffs"; + +find(\&dump, "src"); + +sub dump { + if ($_ eq $Target) { + my $path = $File::Find::name; + print "=== \$path ===\\n"; + open(my $fh, "<", $_) || die "wtf"; + while (my $line = <$fh>) { + print $line; + if ($. > 1000) { last; } + } + } +}