pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedjsonpath
198+ messages / 20 participants
[nested] [flat]
Commits from this thread:
2019-09-25 6dda292d4d Allow datetime values in JsonbValue
2019-09-25 bffe1bd684 Implement jsonpath .datetime() method
2019-09-25 66c74f8b6e Implement parse_datetime() function
2019-09-16 d589f94460 Support for FF1-FF6 datetime format patterns
2019-04-01 0a02e2ae02 GIN support for @@ and @? jsonpath operators
2019-03-16 16d489b0fe Numeric error suppression in jsonpath
2019-03-16 72b6460336 Partial implementation of SQL/JSON path language
* jsonpath
@ 2018-01-07 19:31 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-07 19:31 UTC (permalink / raw)
To: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
Next up in the proposed SQL/JSON feature set I will review the three
jsonpath patches. These are attached, extracted from the patch set
Nikita posted on Jan 2nd.
Note that they depend on the TZH/TZM patch (see separate email thread).
I'd like to be able to commit that patch pretty soon.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments: download all (3 files, .tar.gz)
0004-jsonpath-json-v07.patch (text/x-patch, 92.9K) download
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5f0b254..b263d88 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
@@ -46,6 +46,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 10ac37c..e130987 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;
}
@@ -2553,3 +2558,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 b957705..b4204cd 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 @@ recurse:
/* 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)
@@ -1778,7 +1775,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;
@@ -1842,6 +1839,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 df19db1..6b7367f 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -23,6 +23,12 @@
#include "utils/jsonpath.h"
#include "utils/varlena.h"
+#ifdef JSONPATH_JSON_C
+#define JSONXOID JSONOID
+#else
+#define JSONXOID JSONBOID
+#endif
+
typedef struct JsonPathExecContext
{
List *vars;
@@ -155,6 +161,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
return lfirst(it->lcell);
}
+#ifndef JSONPATH_JSON_C
/*
* Initialize a binary JsonbValue with the given jsonb container.
*/
@@ -167,6 +174,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
return jbv;
}
+#endif
/*
* Transform a JsonbValue into a binary JsonbValue by encoding it to a
@@ -278,7 +286,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
value->val.datetime.typmod = var->typmod;
value->val.datetime.value = computedValue;
break;
- case JSONBOID:
+ case JSONXOID:
{
Jsonb *jb = DatumGetJsonbP(computedValue);
@@ -344,7 +352,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;
@@ -369,7 +377,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);
@@ -430,7 +438,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);
@@ -2372,7 +2380,7 @@ makePassingVars(Jsonb *jb)
jpv->cb_arg = v.val.numeric;
break;
case jbvBinary:
- jpv->typid = JSONBOID;
+ jpv->typid = JSONXOID;
jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
break;
default:
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..720af51
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_json.c
@@ -0,0 +1,20 @@
+#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
+
+#include "jsonpath_exec.c"
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 21b5267..83d6af2 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1859,5 +1859,11 @@ DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contse
DESCR("jsonpath exists");
DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
DESCR("jsonpath predicate");
+DATA(insert OID = 6070 ( "@*" PGNSP PGUID b f f 114 6050 114 0 0 6044 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6071 ( "@?" PGNSP PGUID b f f 114 6050 16 0 0 6043 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6108 ( "@~" PGNSP PGUID b f f 114 6050 16 0 0 6049 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d9a4c37..20cc076 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5549,6 +5549,19 @@ DESCR("implementation of @~ operator");
DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
DESCR("jsonpath predicate test");
+DATA(insert OID = 6043 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6044 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 114 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6045 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6046 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 114 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath query");
+DATA(insert OID = 6049 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6069 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 180461a..78c9be7 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
@@ -149,4 +192,22 @@ extern text *transform_json_string_values(text *json, void *action_state,
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+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 f509b34..aa55615 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -224,10 +224,10 @@ typedef struct
} Jsonb;
/* convenience macros for accessing the root container in a Jsonb datum */
-#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK)
-#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0)
-#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0)
-#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0)
+#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root)
+#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root)
+#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root)
+#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root)
enum jbvType
@@ -276,6 +276,8 @@ struct JsonbValue
struct
{
int nPairs; /* 1 pair, 2 elements */
+ bool uniquified; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
JsonbPair *pairs;
} object; /* Associative container type */
@@ -370,6 +372,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,
@@ -378,6 +382,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 0000000..ee59344
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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))
+
+
+#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
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..5200847
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1649 @@
+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]';
+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]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @? 'strict $.[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? 'strict $.[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @? 'strict $.[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}';
+ ?column?
+----------
+ {"b": 1}
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b": 1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": 1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select json '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select json '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select json '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select json '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select json '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select json '[]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonpath_predicate
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select json 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select json '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()';
+ ?column?
+----------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select json '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()';
+ERROR: SQL/JSON object not found
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:14:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:54:00+00:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:14:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:54:00+10:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select json '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T00:00:00+00:00"
+(3 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-09T21:02:03+00:00"
+(2 rows)
+
+-- time comparison
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:35:00+00:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:36:00+00:00"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:34:00+00:00"
+ "2017-03-10T10:35:00+00:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
+ ?column?
+----------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+ ?column?
+----------
+(0 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 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 096d32d..4ad1e95 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 3c4c4a6..86d445e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -159,6 +159,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 0000000..fe35741
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,359 @@
+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]' @? '$.[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 '{}' @? '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)';
+
+-- 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 > 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';
0003-jsonpath-gin-v07.patch (text/x-patch, 37.7K) download
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index f179bc4..1be32b7 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -536,6 +536,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index 4e1ba10..35c2c7a 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,42 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+typedef enum { eOr, eAnd, eEntry } JsonPathNodeType;
+
+typedef struct JsonPathNode
+{
+ JsonPathNodeType type;
+ union
+ {
+ int nargs;
+ int entry;
+ } val;
+ struct JsonPathNode *args[FLEXIBLE_ARRAY_MEMBER];
+} JsonPathNode;
+
+typedef struct JsonPathExtractionContext
+{
+ Datum *entries;
+ int32 nentries;
+ int32 nallocated;
+ void *(*addKey)(void *path, char *key, int len);
+ bool pathOps;
+ bool lax;
+} JsonPathExtractionContext;
+
+typedef struct JsonPathContext
+{
+ void *path;
+ JsonPathItemType last;
+} JsonPathContext;
+
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_recursive(
+ JsonPathExtractionContext *cxt, JsonPathItem *jsp, bool not,
+ JsonPathContext path);
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -119,6 +154,436 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entries);
}
+/*
+ * Extract JSON path into the 'pathcxt' with filters.
+ * Returns true iff this path is supported by the index opclass.
+ */
+static bool
+gin_extract_jsonpath_path(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ JsonPathContext *pathcxt, List **filters)
+{
+ JsonPathItem next;
+
+ for (;;)
+ {
+ /* save the type of the last item in the path */
+ if (jsp->type != jpiFilter && jsp->type != jpiCurrent)
+ pathcxt->last = jsp->type;
+
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ pathcxt->path = NULL;
+ break;
+
+ case jpiCurrent:
+ break;
+
+ case jpiKey:
+ pathcxt->path = cxt->addKey(pathcxt->path,
+ jsp->content.value.data,
+ jsp->content.value.datalen);
+ break;
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ break;
+
+ case jpiAny:
+ case jpiAnyKey:
+ if (cxt->pathOps)
+ /* jsonb_path_ops doesn't support wildcard paths */
+ return false;
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ JsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = gin_extract_jsonpath_expr_recursive(cxt, &arg, false, *pathcxt);
+
+ 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(JsonPathExtractionContext *cxt, Datum entry)
+{
+ JsonPathNode *node = palloc(offsetof(JsonPathNode, args));
+
+ if (cxt->nentries >= cxt->nallocated)
+ {
+ if (cxt->entries)
+ {
+ cxt->nallocated *= 2;
+ cxt->entries = repalloc(cxt->entries,
+ sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ else
+ {
+ cxt->nallocated = 8;
+ cxt->entries = palloc(sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ }
+
+ node->type = eEntry;
+ node->val.entry = cxt->nentries;
+
+ cxt->entries[cxt->nentries++] = entry;
+
+ return node;
+}
+
+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_from_list(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;
+}
+
+/*
+ * 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,
+ JsonPathContext pathcxt, JsonbValue *scalar)
+{
+ JsonPathNode *node;
+ List *filters = NIL;
+ ListCell *lc;
+
+ if (!gin_extract_jsonpath_path(cxt, jsp, &pathcxt, &filters))
+ return NULL;
+
+ if (cxt->pathOps)
+ {
+ if (scalar)
+ {
+ uint32 hash = (uint32)(uintptr_t) pathcxt.path;
+
+ JsonbHashScalarValue(scalar, &hash);
+ node = gin_jsonpath_make_entry_node(cxt, UInt32GetDatum(hash));
+ }
+ else
+ node = NULL; /* jsonb_path_ops doesn't support EXISTS queries */
+ }
+ else
+ {
+ List *entries = pathcxt.path;
+ List *nodes = NIL;
+
+ if (scalar)
+ {
+ bool lastIsArrayAccessor =
+ pathcxt.last == jpiIndexArray ||
+ pathcxt.last == jpiAnyArray ? GIN_TRUE :
+ pathcxt.last == 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(eOr, 2);
+ node->args[0] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, true));
+ node->args[1] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, false));
+ }
+ else
+ {
+ Datum entry = make_scalar_key(scalar,
+ scalar->type == jbvString &&
+ lastIsArrayAccessor == GIN_TRUE);
+
+ node = gin_jsonpath_make_entry_node(cxt, entry);
+ }
+
+ nodes = lappend(nodes, node);
+ }
+
+ foreach(lc, entries)
+ nodes = lappend(nodes, gin_jsonpath_make_entry_node(cxt,
+ PointerGetDatum(lfirst(lc))));
+
+ if (list_length(nodes) > 0)
+ node = gin_jsonpath_make_expr_node_from_list(eAnd, nodes);
+ else
+ node = NULL; /* need full scan for EXISTS($) queries */
+ }
+
+ if (list_length(filters) <= 0)
+ return node;
+
+ /* construct AND-node for path with filters */
+ if (node)
+ filters = lcons(node, filters);
+
+ return gin_jsonpath_make_expr_node_from_list(eAnd, filters);
+}
+
+/* Extract nodes from the boolean jsonpath expression. */
+static JsonPathNode *
+gin_extract_jsonpath_expr_recursive(JsonPathExtractionContext *cxt,
+ JsonPathItem *jsp, bool not,
+ JsonPathContext path)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd:
+ case jpiOr:
+ {
+ JsonPathItem arg;
+ JsonPathNode *larg;
+ JsonPathNode *rarg;
+ JsonPathNode *node;
+ JsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? eAnd : eOr;
+ node = gin_jsonpath_make_expr_node(type, 2);
+ node->args[0] = larg;
+ node->args[1] = rarg;
+
+ return node;
+ }
+
+ case jpiNot:
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ return gin_extract_jsonpath_expr_recursive(cxt, &arg, !not, path);
+ }
+
+ case jpiExists:
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return false;
+
+ 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;
+ }
+}
+
+/* Append key name to a path. */
+static void *
+gin_jsonb_ops_add_key(void *path, char *key, int len)
+{
+ return lappend((List *) path, DatumGetPointer(
+ make_text_key(JGINFLAG_KEY, key, len)));
+}
+
+/* Combine existing path hash with next key hash. */
+static void *
+gin_jsonb_path_ops_add_key(void *path, char *key, int len)
+{
+ JsonbValue jbv;
+ uint32 hash = (uint32)(uintptr_t) path;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = key;
+ jbv.val.string.len = len;
+
+ JsonbHashScalarValue(&jbv, &hash);
+
+ return (void *)(uintptr_t) hash;
+}
+
+static Datum *
+gin_extract_jsonpath_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ JsonPathExtractionContext cxt = { 0 };
+ JsonPathItem root;
+ JsonPathNode *node;
+ JsonPathContext path = { NULL, GIN_FALSE };
+
+ cxt.addKey = pathOps ? gin_jsonb_path_ops_add_key : gin_jsonb_ops_add_key;
+ cxt.pathOps = pathOps;
+ cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? gin_extract_jsonpath_node(&cxt, &root, path, NULL)
+ : gin_extract_jsonpath_expr_recursive(&cxt, &root, false, path);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ *nentries = cxt.nentries;
+ *extra_data = palloc(sizeof(**extra_data) * cxt.nentries);
+ **extra_data = (Pointer) node;
+
+ return cxt.entries;
+}
+
+static GinTernaryValue
+gin_execute_jsonpath(JsonPathNode *node, GinTernaryValue *check)
+{
+ 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);
+ 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);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eEntry:
+ return check[node->val.entry] ? GIN_MAYBE : GIN_FALSE;
+
+ default:
+ elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+ return GIN_FALSE;
+ }
+}
+
Datum
gin_extract_jsonb_query(PG_FUNCTION_ARGS)
{
@@ -181,6 +646,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 +676,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 +733,13 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check) != GIN_FALSE;
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +754,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 +791,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);
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -432,18 +921,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 +962,40 @@ 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)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1008,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);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_GIN_TERNARY_VALUE(res);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index d877079..026064e 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -821,11 +821,15 @@ DATA(insert ( 4036 3802 3802 7 s 3246 2742 0 ));
DATA(insert ( 4036 3802 25 9 s 3247 2742 0 ));
DATA(insert ( 4036 3802 1009 10 s 3248 2742 0 ));
DATA(insert ( 4036 3802 1009 11 s 3249 2742 0 ));
+DATA(insert ( 4036 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4036 3802 6050 16 s 6107 2742 0 ));
/*
* GIN jsonb_path_ops
*/
DATA(insert ( 4037 3802 3802 7 s 3246 2742 0 ));
+DATA(insert ( 4037 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4037 3802 6050 16 s 6107 2742 0 ));
/*
* SP-GiST range_ops
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 4473b3e..f509b34 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 d06bb14..87dae0d 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 465195a..178e06f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2708,6 +2708,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
@@ -2783,6 +2891,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ QUERY PLAN
+-----------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @~ '($."wait" == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @~ '($."wait" == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -2933,6 +3231,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
1012
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
RESET enable_seqscan;
DROP INDEX jidx;
-- nested tests
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f2..db0bab2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1817,6 +1817,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1880,7 +1882,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(123 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 903e5ef..b153e62 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -733,6 +733,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
@@ -751,6 +769,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -800,6 +851,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
-- exercise GIN_SEARCH_MODE_ALL
SELECT count(*) FROM testjsonb WHERE j @> '{}';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
RESET enable_seqscan;
DROP INDEX jidx;
0002-jsonpath-v07.patch (text/x-patch, 234.5K) download
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9aa9b28..0011769 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -149,6 +149,12 @@
</row>
<row>
+ <entry><type>jsonpath</type></entry>
+ <entry></entry>
+ <entry>binary JSON path</entry>
+ </row>
+
+ <row>
<entry><type>line</type></entry>
<entry></entry>
<entry>infinite line on a plane</entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3978747..796b2f2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11066,6 +11066,7 @@ table2-mapping
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11075,6 +11076,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11083,6 +11085,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11090,6 +11093,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11097,6 +11101,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11104,6 +11109,7 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object at specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
@@ -11111,10 +11117,39 @@ table2-mapping
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object at specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json *? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Does JSON path return any item for a specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..f179bc4 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,16 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
compared using the default database collation.
</para>
</sect2>
+
+ <sect2 id="json-path">
+ <title><type>jsonpath</type></title>
+ <indexterm>
+ <primary>json</primary>
+ <secondary>path</secondary>
+ </indexterm>
+
+ <para>
+ TODO
+ </para>
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index aab676d..acdba65 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -139,6 +139,9 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
utils/errcodes.h: utils/generate-errcodes.pl utils/errcodes.txt
$(MAKE) -C utils errcodes.h
+utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y
+ $(MAKE) -C utils/adt jsonpath_gram.h
+
# see explanation in parser/Makefile
utils/fmgrprotos.h: utils/fmgroids.h ;
@@ -169,7 +172,7 @@ submake-schemapg:
.PHONY: generated-headers
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h $(top_builddir)/src/include/utils/jsonpath_gram.h
$(top_builddir)/src/include/parser/gram.h: parser/gram.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -186,6 +189,11 @@ $(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 $<)" .
+
$(top_builddir)/src/include/utils/errcodes.h: utils/errcodes.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
cd '$(dir $@)' && rm -f $(notdir $@) && \
@@ -220,6 +228,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h
$(MAKE) -C utils fmgrtab.c fmgroids.h fmgrprotos.h errcodes.h
+ $(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
@@ -308,6 +317,7 @@ endif
clean:
rm -f $(LOCALOBJS) postgres$(X) $(POSTGRES_IMP) \
$(top_builddir)/src/include/parser/gram.h \
+ $(top_builddir)/src/include/utils/jsonpath_gram.h \
$(top_builddir)/src/include/catalog/schemapg.h \
$(top_builddir)/src/include/storage/lwlocknames.h \
$(top_builddir)/src/include/utils/fmgroids.h \
@@ -344,6 +354,7 @@ maintainer-clean: distclean
utils/fmgrtab.c \
utils/errcodes.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 cb2026c..060a198 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 1fb0184..5f0b254 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -31,6 +32,26 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution
+# tarball, so they are not cleaned here.
+clean distclean maintainer-clean:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 307b5e8..83f7b27 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,13 +41,6 @@
#endif
-static int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
-static int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
-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)
@@ -1234,7 +1227,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)
@@ -1249,7 +1242,7 @@ tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
* If out of this range, leave as UTC (in practice that could only happen
* if pg_time_t is just 32 bits) - thomas 97/05/27
*/
-static int
+int
time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec)
{
tm->tm_hour = time / USECS_PER_HOUR;
@@ -1400,7 +1393,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] = {
@@ -1939,7 +1932,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) *
@@ -2073,7 +2066,7 @@ timetztypmodout(PG_FUNCTION_ARGS)
/* timetz2tm()
* Convert TIME WITH TIME ZONE data type to POSIX time structure.
*/
-static int
+int
timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp)
{
TimeOffset trem = time->time;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e7ca249..57e9703 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -953,6 +954,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
@@ -967,7 +972,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);
@@ -984,8 +990,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,
- 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, 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);
@@ -2974,13 +2980,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;
@@ -3262,6 +3270,120 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "date time format")));
+ }
+}
+
+/* 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 */
@@ -3563,7 +3685,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3598,7 +3721,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3620,6 +3744,155 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
@@ -3631,14 +3904,20 @@ 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len;
+ char *fmt_tmp = NULL;
char *date_str;
int fmask;
@@ -3649,15 +3928,15 @@ do_to_timestamp(text *date_txt, text *fmt,
*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)
{
/*
@@ -3687,13 +3966,18 @@ 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);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
- pfree(fmt_str);
if (!incache)
pfree(format);
}
+ if (fmt_tmp)
+ pfree(fmt_tmp);
+
DEBUG_TMFC(&tmfc);
/*
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index fcce26e..10ac37c 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1504,11 +1504,69 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
break;
case JSONTYPE_DATE:
{
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, DATEOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMP:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMPTZ:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_JSON:
+ /* JSON and JSONB output will already be escaped */
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
+ appendStringInfoString(result, outputstr);
+ pfree(outputstr);
+ break;
+ case JSONTYPE_CAST:
+ /* outfuncoid refers to a cast function, not an output function */
+ jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
+ outputstr = text_to_cstring(jsontext);
+ appendStringInfoString(result, outputstr);
+ pfree(outputstr);
+ pfree(jsontext);
+ break;
+ default:
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
+ escape_json(result, outputstr);
+ pfree(outputstr);
+ break;
+ }
+}
+
+/*
+ * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
+ * optionally preallocated buffer 'buf'.
+ */
+char *
+JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+{
+ if (!buf)
+ buf = palloc(MAXDATELEN + 1);
+
+ switch (typid)
+ {
+ case DATEOID:
+ {
DateADT date;
struct pg_tm tm;
- char buf[MAXDATELEN + 1];
- date = DatumGetDateADT(val);
+ date = DatumGetDateADT(value);
+
/* Same as date_out(), but forcing DateStyle */
if (DATE_NOT_FINITE(date))
EncodeSpecialDate(date, buf);
@@ -1518,17 +1576,40 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
&(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
EncodeDateOnly(&tm, USE_XSD_DATES, buf);
}
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_TIMESTAMP:
+ case TIMEOID:
+ {
+ TimeADT time = DatumGetTimeADT(value);
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+
+ /* Same as time_out(), but forcing DateStyle */
+ time2tm(time, tm, &fsec);
+ EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf);
+ }
+ break;
+ case TIMETZOID:
+ {
+ TimeTzADT *time = DatumGetTimeTzADTP(value);
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+ int tz;
+
+ /* Same as timetz_out(), but forcing DateStyle */
+ timetz2tm(time, tm, &fsec, &tz);
+ EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf);
+ }
+ break;
+ case TIMESTAMPOID:
{
Timestamp timestamp;
struct pg_tm tm;
fsec_t fsec;
- char buf[MAXDATELEN + 1];
- timestamp = DatumGetTimestamp(val);
+ timestamp = DatumGetTimestamp(value);
/* Same as timestamp_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
@@ -1538,19 +1619,17 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_TIMESTAMPTZ:
+ case TIMESTAMPTZOID:
{
TimestampTz timestamp;
struct pg_tm tm;
int tz;
fsec_t fsec;
const char *tzn = NULL;
- char buf[MAXDATELEN + 1];
- timestamp = DatumGetTimestampTz(val);
+ timestamp = DatumGetTimestampTz(value);
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
@@ -1560,29 +1639,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_JSON:
- /* JSON and JSONB output will already be escaped */
- outputstr = OidOutputFunctionCall(outfuncoid, val);
- appendStringInfoString(result, outputstr);
- pfree(outputstr);
- break;
- case JSONTYPE_CAST:
- /* outfuncoid refers to a cast function, not an output function */
- jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
- outputstr = text_to_cstring(jsontext);
- appendStringInfoString(result, outputstr);
- pfree(outputstr);
- pfree(jsontext);
- break;
default:
- outputstr = OidOutputFunctionCall(outfuncoid, val);
- escape_json(result, outputstr);
- pfree(outputstr);
- break;
+ elog(ERROR, "unknown jsonb value datetime type oid %d", typid);
+ return NULL;
}
+
+ return buf;
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 4b2a541..78a9882 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -786,71 +786,19 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
}
break;
case JSONBTYPE_DATE:
- {
- DateADT date;
- struct pg_tm tm;
- char buf[MAXDATELEN + 1];
-
- date = DatumGetDateADT(val);
- /* Same as date_out(), but forcing DateStyle */
- if (DATE_NOT_FINITE(date))
- EncodeSpecialDate(date, buf);
- else
- {
- j2date(date + POSTGRES_EPOCH_JDATE,
- &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
- EncodeDateOnly(&tm, USE_XSD_DATES, buf);
- }
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMP:
- {
- Timestamp timestamp;
- struct pg_tm tm;
- fsec_t fsec;
- char buf[MAXDATELEN + 1];
-
- timestamp = DatumGetTimestamp(val);
- /* Same as timestamp_out(), but forcing DateStyle */
- if (TIMESTAMP_NOT_FINITE(timestamp))
- EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
- EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf);
- else
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMPTZ:
- {
- TimestampTz timestamp;
- struct pg_tm tm;
- int tz;
- fsec_t fsec;
- const char *tzn = NULL;
- char buf[MAXDATELEN + 1];
-
- timestamp = DatumGetTimestampTz(val);
- /* 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)
- EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
- else
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_JSONCAST:
case JSONBTYPE_JSON:
@@ -1897,3 +1845,27 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(out);
}
+
+/*
+ * Extract scalar value from raw-scalar pseudo-array jsonb.
+ */
+JsonbValue *
+JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
+{
+ JsonbIterator *it = JsonbIteratorInit(jbc);
+ JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
+ JsonbValue tmp;
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+ Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);
+
+ tok = JsonbIteratorNext(&it, res, true);
+ Assert (tok == WJB_ELEM);
+ Assert(IsAJsonbScalar(res));
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert (tok == WJB_END_ARRAY);
+
+ return res;
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index d425f32..b957705 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/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..1d51d8b
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,866 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ bool allowCurrent, bool insideArraySubscript)
+{
+ /* position from begining of jsonpath data */
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ int32 chld, next;
+
+ check_stack_depth();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ {
+ 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));
+
+ chld = flattenJsonPathParseItem(buf, item->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld;
+ chld = flattenJsonPathParseItem(buf, item->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ *(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,
+ allowCurrent,
+ insideArraySubscript);
+ *(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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ item->type == jpiFilter ||
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (!allowCurrent)
+ ereport(ERROR,
+ (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;
+ 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, true);
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ true, true);
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ *(int32*)(buf->data + next) =
+ flattenJsonPathParseItem(buf, item->next, allowCurrent,
+ insideArraySubscript);
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, false, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ 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);
+ else
+ 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(", 10);
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+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:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ case jpiDatetime:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus ||
+ v->type == jpiDatetime
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..df19db1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2616 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+typedef struct JsonPathExecContext
+{
+ List *vars;
+ bool lax;
+ JsonbValue *root; /* for $ evaluation */
+ int innermostArraySize; /* for LAST array index evaluation */
+} JsonPathExecContext;
+
+typedef struct JsonValueListIterator
+{
+ ListCell *lcell;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
+
+
+static inline void
+JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+{
+ if (jvl->singleton)
+ {
+ jvl->list = list_make2(jvl->singleton, jbv);
+ jvl->singleton = NULL;
+ }
+ else if (!jvl->list)
+ jvl->singleton = jbv;
+ else
+ jvl->list = lappend(jvl->list, jbv);
+}
+
+static inline void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (jvl1->singleton)
+ {
+ if (jvl2.singleton)
+ jvl1->list = list_make2(jvl1->singleton, jvl2.singleton);
+ else
+ jvl1->list = lcons(jvl1->singleton, jvl2.list);
+
+ jvl1->singleton = NULL;
+ }
+ else if (jvl2.singleton)
+ {
+ if (jvl1->list)
+ jvl1->list = lappend(jvl1->list, jvl2.singleton);
+ else
+ jvl1->singleton = jvl2.singleton;
+ }
+ else if (jvl1->list)
+ jvl1->list = list_concat(jvl1->list, jvl2.list);
+ else
+ jvl1->list = jvl2.list;
+}
+
+static inline int
+JsonValueListLength(const JsonValueList *jvl)
+{
+ return jvl->singleton ? 1 : list_length(jvl->list);
+}
+
+static inline bool
+JsonValueListIsEmpty(JsonValueList *jvl)
+{
+ return !jvl->singleton && list_length(jvl->list) <= 0;
+}
+
+static inline JsonbValue *
+JsonValueListHead(JsonValueList *jvl)
+{
+ return jvl->singleton ? jvl->singleton : linitial(jvl->list);
+}
+
+static inline void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static inline List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ if (jvl->singleton)
+ return list_make1(jvl->singleton);
+
+ return jvl->list;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static void
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ foreach(cell, vars)
+ {
+ var = (JsonPathVariable*)lfirst(cell);
+
+ if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) &&
+ !strncmp(varName, VARDATA_ANY(var->varName), varNameLength))
+ break;
+
+ var = NULL;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return;
+ }
+
+ switch(var->typid)
+ {
+ case BOOLOID:
+ value->type = jbvBool;
+ value->val.boolean = DatumGetBool(computedValue);
+ break;
+ case NUMERICOID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(computedValue);
+ break;
+ break;
+ case INT2OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int2_numeric, computedValue));
+ break;
+ case INT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int4_numeric, computedValue));
+ break;
+ case INT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric, computedValue));
+ break;
+ case FLOAT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case FLOAT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ value->type = jbvString;
+ value->val.string.val = VARDATA_ANY(computedValue);
+ value->val.string.len = VARSIZE_ANY_EXHDR(computedValue);
+ break;
+ case 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);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static void
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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, cxt->vars, value);
+ break;
+ default:
+ elog(ERROR, "Wrong type");
+ }
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathExecResult
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jperOk : jperNotFound;
+
+ return jperError;
+ }
+
+ 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;
+ 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;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jperError;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jperOk : jperNotFound;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathExecResult
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jperError;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jperNotFound;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jperError;
+ }
+
+ return res ? jperOk : jperNotFound;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (cxt->lax)
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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 satifistfying
+ * condition. In strict mode all pairs should be comparable, otherwise an error
+ * is returned.
+ */
+static JsonPathExecResult
+executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperError;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ 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 (cxt->lax)
+ return jperOk;
+
+ found = true;
+ }
+ else if (res == jperError)
+ {
+ if (!cxt->lax)
+ return jperError;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jperOk;
+
+ if (error) /* possible only in lax mode */
+ return jperError;
+
+ return jperNotFound;
+}
+
+/*
+ * 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ 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);
+
+ return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false);
+}
+
+/*
+ * Execute unary arithemitc 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursivly iterate over jsonb objects/arrays
+ */
+ 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 */
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathExecResult
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ 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 (JsonValueListLength(&rseq) != 1)
+ return jperError;
+
+ initial = JsonValueListHead(&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;
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ 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;
+}
+
+static JsonPathExecResult
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ 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;
+}
+
+/*
+ * Try to parse datetime text with the specified datetime template.
+ * Returns 'value' datum, its type 'typid' and 'typmod'.
+ */
+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;
+}
+
+/*
+ * Convert execution status 'res' to a boolean JSON item and execute next
+ * jsonpath if 'needBool' is false:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => null (errors are converted to NULL values)
+ */
+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; /* simply return status */
+ }
+
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found, bool needBool)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+
+ check_stack_depth();
+
+ switch(jsp->type) {
+ 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 jperOk:
+ res = jperNotFound;
+ break;
+ case jperNotFound:
+ res = jperOk;
+ break;
+ default:
+ 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)
+ {
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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)
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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;
+ }
+
+ JsonbExtractScalar(jb->val.binary.data, v);
+ }
+ else
+ v = jb;
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+ break;
+ }
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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);
+ break;
+
+ case jpiIndexArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonbArraySize(jb);
+ bool binary = jb->type == jbvBinary;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!cxt->lax &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ 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;
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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);
+
+ if (cxt->lax)
+ res = recursiveExecute(cxt, &elem, jb, NULL);
+ else
+ {
+ 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;
+ }
+
+ res = appendBoolResult(cxt, jsp, found, res, needBool);
+ break;
+ case jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ {
+ 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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ 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;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ 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);
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ 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);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ for (; elem < last; elem++)
+ {
+ res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
+
+ 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, false);
+ if (jperIsError(res))
+ break;
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+
+ 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)
+{
+ if (cxt->lax && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+static inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (cxt->lax)
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* 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:
+ case jpiIndexArray:
+ jb = wrapItem(jb);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Execute boolean-valued jsonpath expression. Boolean items are not appended
+ * to the result list, only return code determines result:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => NULL (errors are converted to NULL values)
+ */
+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
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONBOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static 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)
+{
+ 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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ throwJsonPathError(res);
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)))
+ {
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..e62a0b5
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,477 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+#include "utils/jsonpath_scanner.h"
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+static JsonPathParseItem*
+makeItemType(int type)
+{
+ JsonPathParseItem* v = palloc(sizeof(*v));
+
+ v->type = type;
+ v->next = NULL;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemString(string *s)
+{
+ JsonPathParseItem *v;
+
+ if (s == NULL)
+ {
+ v = makeItemType(jpiNull);
+ }
+ else
+ {
+ v = makeItemType(jpiString);
+ v->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first > 0) ? first : 0;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
+{
+ JsonPathParseItem *v = makeItemType(jpiLikeRegex);
+ int i;
+
+ 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;
+ break;
+ case 's':
+ v->value.like_regex.flags &= ~JSP_REGEX_MLINE;
+ v->value.like_regex.flags |= JSP_REGEX_SLINE;
+ break;
+ case 'm':
+ v->value.like_regex.flags &= ~JSP_REGEX_SLINE;
+ v->value.like_regex.flags |= JSP_REGEX_MLINE;
+ break;
+ case 'x':
+ v->value.like_regex.flags |= JSP_REGEX_WSPACE;
+ break;
+ default:
+ yyerror(NULL, "unrecognized flag of LIKE_REGEX predicate");
+ break;
+ }
+ }
+
+ return v;
+}
+
+%}
+
+/* BISON Declarations */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> 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 <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | pexpr 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
+ { $$ = makeItemLikeRegex($1, &$3, &$5); };
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+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); }
+ ;
+
+index_elem:
+ pexpr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | pexpr TO_P pexpr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+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)); }
+ ;
+
+accessor_op:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' 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); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..aad4aa2
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,557 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 xVARQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
+blank [ \t\n\r\f]
+unicode \\u[0-9A-Fa-f]{4}
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\") {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\[\"\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index e858b59..2ba5f63 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -335,7 +335,7 @@ RE_execute(regex_t *re, char *dat, int dat_len,
* Both pattern and data are given in the database encoding. We internally
* convert to array of pg_wchar which is what Spencer's regex package wants.
*/
-static bool
+bool
RE_compile_and_execute(text *text_re, char *dat, int dat_len,
int cflags, Oid collation,
int nmatch, regmatch_t *pmatch)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5797aaa..d4d5583 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 76fe79e..4fe2c90 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -205,6 +205,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
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index ff9b470..21b5267 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1853,5 +1853,11 @@ DATA(insert OID = 3286 ( "-" PGNSP PGUID b f f 3802 23 3802 0 0 3303 - - ));
DESCR("delete array element");
DATA(insert OID = 3287 ( "#-" PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_delete_path - - ));
DESCR("delete path");
+DATA(insert OID = 6075 ( "@*" PGNSP PGUID b f f 3802 6050 3802 0 0 6055 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 830bab3..d9a4c37 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5531,6 +5531,24 @@ DESCR("list of files in the WAL directory");
DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
DESCR("hash partition CHECK constraint");
+/* jsonpath */
+DATA(insert OID = 6052 ( jsonpath_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 6050 "2275" _null_ _null_ _null_ _null_ _null_ jsonpath_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6053 ( jsonpath_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6050" _null_ _null_ _null_ _null_ _null_ jsonpath_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6054 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6055 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 3802 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6056 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6057 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 3802 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath object test");
+DATA(insert OID = 6073 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e355144..708e64a 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -638,6 +638,12 @@ DESCR("Binary JSON");
#define JSONBOID 3802
DATA(insert OID = 3807 ( _jsonb PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+/* jsonpath */
+DATA(insert OID = 6050 ( jsonpath PGNSP PGUID -1 f b U f t \054 0 0 6051 jsonpath_in jsonpath_out - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("JSON Path");
+#define JSONPATHOID 6050
+DATA(insert OID = 6051 ( _jsonpath PGNSP PGUID -1 f b A f t \054 0 6050 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+
DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
DESCR("txid snapshot");
DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 906ae5e..0d59e78 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..8a2bf03 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -173,4 +173,8 @@ 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 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/date.h b/src/include/utils/date.h
index 0736a72..e30c6cf 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -17,6 +17,7 @@
#include <math.h>
#include "fmgr.h"
+#include "utils/timestamp.h"
typedef int32 DateADT;
@@ -73,5 +74,10 @@ extern void EncodeSpecialDate(DateADT dt, char *str);
extern DateADT GetSQLCurrentDate(void);
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 7968569..57b263b 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 8eaf2c3..119df00 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
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 4336823..180461a 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -147,4 +147,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);
+
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index d639bbc..4473b3e 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;
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +281,19 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +391,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 0000000..d06bb14
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,270 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+extern JsonPathParseResult* parsejsonpath(const char *str, int len);
+
+/*
+ * Evaluation of jsonpath
+ */
+
+typedef enum JsonPathExecStatus
+{
+ 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)
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **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 0000000..c57255c
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1630 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$.[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$.[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $.[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$.[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $.[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?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,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(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"}}
+(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"}}
+(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"}}
+(3 rows)
+
+select jsonb 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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 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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+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 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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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 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"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+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"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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 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"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+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"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..7b20b8a
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,784 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$[*]'::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 '$.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 '$+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 ? ($.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."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
+--------------------
+ $."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 '$.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;
+ ^
+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
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ jsonpath
+--------------------------
+ $?(@ starts with $"var")
+(1 row)
+
+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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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;
+ 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 e224977..096d32d 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 9fc5f1a..3c4c4a6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -158,6 +158,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 0000000..0078c60
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,366 @@
+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]' @? '$.[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)');
+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}';
+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
+ 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);
+
+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 jsonb '{"a": [2]}' @* 'lax $.a * 3';
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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 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 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 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 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 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 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 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';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..14d11dc
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,142 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+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;
+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 '$+1'::jsonpath;
+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;
+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 ? (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;
+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;
+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;
+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;
+
+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 '$ < 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;
+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;
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-07 20:00 Pavel Stehule <pavel.stehule@gmail.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Pavel Stehule @ 2018-01-07 20:00 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-01-07 20:31 GMT+01:00 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>:
>
> Next up in the proposed SQL/JSON feature set I will review the three
> jsonpath patches. These are attached, extracted from the patch set
> Nikita posted on Jan 2nd.
>
>
> Note that they depend on the TZH/TZM patch (see separate email thread).
> I'd like to be able to commit that patch pretty soon.
>
I did few tests - and it is working very well.
regards
Pavel
>
> cheers
>
>
> andrew
>
>
> --
> Andrew Dunstan https://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-07 20:14 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-07 20:14 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
On 01/07/2018 03:00 PM, Pavel Stehule wrote:
>
>
> 2018-01-07 20:31 GMT+01:00 Andrew Dunstan
> <andrew.dunstan@2ndquadrant.com <mailto:andrew.dunstan@2ndquadrant.com>>:
>
>
> Next up in the proposed SQL/JSON feature set I will review the three
> jsonpath patches. These are attached, extracted from the patch set
> Nikita posted on Jan 2nd.
>
>
> Note that they depend on the TZH/TZM patch (see separate email
> thread).
> I'd like to be able to commit that patch pretty soon.
>
>
> I did few tests - and it is working very well.
>
>
You mean on the revised TZH/TZM patch that I posted?
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-07 20:16 Pavel Stehule <pavel.stehule@gmail.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Pavel Stehule @ 2018-01-07 20:16 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-01-07 21:14 GMT+01:00 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>:
>
>
> On 01/07/2018 03:00 PM, Pavel Stehule wrote:
> >
> >
> > 2018-01-07 20:31 GMT+01:00 Andrew Dunstan
> > <andrew.dunstan@2ndquadrant.com <mailto:andrew.dunstan@2ndquadrant.com
> >>:
> >
> >
> > Next up in the proposed SQL/JSON feature set I will review the three
> > jsonpath patches. These are attached, extracted from the patch set
> > Nikita posted on Jan 2nd.
> >
> >
> > Note that they depend on the TZH/TZM patch (see separate email
> > thread).
> > I'd like to be able to commit that patch pretty soon.
> >
> >
> > I did few tests - and it is working very well.
> >
> >
>
>
> You mean on the revised TZH/TZM patch that I posted?
>
no - I tested jsonpath implementation
Pavel
>
> cheers
>
> andrew
>
> --
> Andrew Dunstan https://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-07 20:20 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-07 20:20 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
On 01/07/2018 03:16 PM, Pavel Stehule wrote:
>
>
> 2018-01-07 21:14 GMT+01:00 Andrew Dunstan
> <andrew.dunstan@2ndquadrant.com <mailto:andrew.dunstan@2ndquadrant.com>>:
>
>
>
> On 01/07/2018 03:00 PM, Pavel Stehule wrote:
> >
> >
> > 2018-01-07 20:31 GMT+01:00 Andrew Dunstan
> > <andrew.dunstan@2ndquadrant.com
> <mailto:andrew.dunstan@2ndquadrant.com>
> <mailto:andrew.dunstan@2ndquadrant.com
> <mailto:andrew.dunstan@2ndquadrant.com>>>:
> >
> >
> > Next up in the proposed SQL/JSON feature set I will review
> the three
> > jsonpath patches. These are attached, extracted from the
> patch set
> > Nikita posted on Jan 2nd.
> >
> >
> > Note that they depend on the TZH/TZM patch (see separate email
> > thread).
> > I'd like to be able to commit that patch pretty soon.
> >
> >
> > I did few tests - and it is working very well.
> >
> >
>
>
> You mean on the revised TZH/TZM patch that I posted?
>
>
> no - I tested jsonpath implementation
>
>
OK. You can help by also reviewing and testing the small patch at
<https://www.postgresql.org/message-id/f16a6408-45fe-b299-02b0-41e50a1c3023%402ndQuadrant.com;
thanks
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-07 20:23 Pavel Stehule <pavel.stehule@gmail.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Pavel Stehule @ 2018-01-07 20:23 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-01-07 21:20 GMT+01:00 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>:
>
>
> On 01/07/2018 03:16 PM, Pavel Stehule wrote:
> >
> >
> > 2018-01-07 21:14 GMT+01:00 Andrew Dunstan
> > <andrew.dunstan@2ndquadrant.com <mailto:andrew.dunstan@2ndquadrant.com
> >>:
> >
> >
> >
> > On 01/07/2018 03:00 PM, Pavel Stehule wrote:
> > >
> > >
> > > 2018-01-07 20:31 GMT+01:00 Andrew Dunstan
> > > <andrew.dunstan@2ndquadrant.com
> > <mailto:andrew.dunstan@2ndquadrant.com>
> > <mailto:andrew.dunstan@2ndquadrant.com
> > <mailto:andrew.dunstan@2ndquadrant.com>>>:
> > >
> > >
> > > Next up in the proposed SQL/JSON feature set I will review
> > the three
> > > jsonpath patches. These are attached, extracted from the
> > patch set
> > > Nikita posted on Jan 2nd.
> > >
> > >
> > > Note that they depend on the TZH/TZM patch (see separate email
> > > thread).
> > > I'd like to be able to commit that patch pretty soon.
> > >
> > >
> > > I did few tests - and it is working very well.
> > >
> > >
> >
> >
> > You mean on the revised TZH/TZM patch that I posted?
> >
> >
> > no - I tested jsonpath implementation
> >
> >
>
>
> OK. You can help by also reviewing and testing the small patch at
> <https://www.postgresql.org/message-id/f16a6408-45fe-b299-
> 02b0-41e50a1c3023%402ndQuadrant.com>
>
I'll look there
Regards
Pavel
> thanks
>
> cheers
>
> andrew
>
> --
> Andrew Dunstan https://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-10 22:42 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Pavel Stehule <pavel.stehule@gmail.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-01-10 22:42 UTC (permalink / raw)
To: Pavel Stehule <pavel.stehule@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
Attached new 8th version of jsonpath related patches. Complete
documentation is still missing.
The first 4 small patches are necessary datetime handling in jsonpath:
1. simple refactoring, extracted function that will be used later in
jsonpath
2. throw an error when the input or format string contains trailing elements
3. avoid unnecessary cstring to text conversions
4. add function for automatic datetime type recognition by the presence
of formatting components
Should they be posted in a separate thread?
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (7 files, .tar.gz)
0001-extract-JsonEncodeDateTime-v08.patch (text/x-patch, 9.9K) download
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 95a9998..747ef49 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,8 +41,6 @@
#endif
-static int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
-static int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
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);
@@ -1249,7 +1247,7 @@ tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
* If out of this range, leave as UTC (in practice that could only happen
* if pg_time_t is just 32 bits) - thomas 97/05/27
*/
-static int
+int
time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec)
{
tm->tm_hour = time / USECS_PER_HOUR;
@@ -2073,7 +2071,7 @@ timetztypmodout(PG_FUNCTION_ARGS)
/* timetz2tm()
* Convert TIME WITH TIME ZONE data type to POSIX time structure.
*/
-static int
+int
timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp)
{
TimeOffset trem = time->time;
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 151345a..97a5b85 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1504,11 +1504,69 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
break;
case JSONTYPE_DATE:
{
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, DATEOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMP:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_TIMESTAMPTZ:
+ {
+ char buf[MAXDATELEN + 1];
+
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ appendStringInfo(result, "\"%s\"", buf);
+ }
+ break;
+ case JSONTYPE_JSON:
+ /* JSON and JSONB output will already be escaped */
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
+ appendStringInfoString(result, outputstr);
+ pfree(outputstr);
+ break;
+ case JSONTYPE_CAST:
+ /* outfuncoid refers to a cast function, not an output function */
+ jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
+ outputstr = text_to_cstring(jsontext);
+ appendStringInfoString(result, outputstr);
+ pfree(outputstr);
+ pfree(jsontext);
+ break;
+ default:
+ outputstr = OidOutputFunctionCall(outfuncoid, val);
+ escape_json(result, outputstr);
+ pfree(outputstr);
+ break;
+ }
+}
+
+/*
+ * Encode 'value' of datetime type 'typid' into JSON string in ISO format using
+ * optionally preallocated buffer 'buf'.
+ */
+char *
+JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+{
+ if (!buf)
+ buf = palloc(MAXDATELEN + 1);
+
+ switch (typid)
+ {
+ case DATEOID:
+ {
DateADT date;
struct pg_tm tm;
- char buf[MAXDATELEN + 1];
- date = DatumGetDateADT(val);
+ date = DatumGetDateADT(value);
+
/* Same as date_out(), but forcing DateStyle */
if (DATE_NOT_FINITE(date))
EncodeSpecialDate(date, buf);
@@ -1518,17 +1576,40 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
&(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
EncodeDateOnly(&tm, USE_XSD_DATES, buf);
}
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_TIMESTAMP:
+ case TIMEOID:
+ {
+ TimeADT time = DatumGetTimeADT(value);
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+
+ /* Same as time_out(), but forcing DateStyle */
+ time2tm(time, tm, &fsec);
+ EncodeTimeOnly(tm, fsec, false, 0, USE_XSD_DATES, buf);
+ }
+ break;
+ case TIMETZOID:
+ {
+ TimeTzADT *time = DatumGetTimeTzADTP(value);
+ struct pg_tm tt,
+ *tm = &tt;
+ fsec_t fsec;
+ int tz;
+
+ /* Same as timetz_out(), but forcing DateStyle */
+ timetz2tm(time, tm, &fsec, &tz);
+ EncodeTimeOnly(tm, fsec, true, tz, USE_XSD_DATES, buf);
+ }
+ break;
+ case TIMESTAMPOID:
{
Timestamp timestamp;
struct pg_tm tm;
fsec_t fsec;
- char buf[MAXDATELEN + 1];
- timestamp = DatumGetTimestamp(val);
+ timestamp = DatumGetTimestamp(value);
/* Same as timestamp_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
@@ -1538,19 +1619,17 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_TIMESTAMPTZ:
+ case TIMESTAMPTZOID:
{
TimestampTz timestamp;
struct pg_tm tm;
int tz;
fsec_t fsec;
const char *tzn = NULL;
- char buf[MAXDATELEN + 1];
- timestamp = DatumGetTimestampTz(val);
+ timestamp = DatumGetTimestampTz(value);
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
@@ -1560,29 +1639,14 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
- appendStringInfo(result, "\"%s\"", buf);
}
break;
- case JSONTYPE_JSON:
- /* JSON and JSONB output will already be escaped */
- outputstr = OidOutputFunctionCall(outfuncoid, val);
- appendStringInfoString(result, outputstr);
- pfree(outputstr);
- break;
- case JSONTYPE_CAST:
- /* outfuncoid refers to a cast function, not an output function */
- jsontext = DatumGetTextPP(OidFunctionCall1(outfuncoid, val));
- outputstr = text_to_cstring(jsontext);
- appendStringInfoString(result, outputstr);
- pfree(outputstr);
- pfree(jsontext);
- break;
default:
- outputstr = OidOutputFunctionCall(outfuncoid, val);
- escape_json(result, outputstr);
- pfree(outputstr);
- break;
+ elog(ERROR, "unknown jsonb value datetime type oid %d", typid);
+ return NULL;
}
+
+ return buf;
}
/*
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 014e7aa..0f70180 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -786,71 +786,19 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result,
}
break;
case JSONBTYPE_DATE:
- {
- DateADT date;
- struct pg_tm tm;
- char buf[MAXDATELEN + 1];
-
- date = DatumGetDateADT(val);
- /* Same as date_out(), but forcing DateStyle */
- if (DATE_NOT_FINITE(date))
- EncodeSpecialDate(date, buf);
- else
- {
- j2date(date + POSTGRES_EPOCH_JDATE,
- &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
- EncodeDateOnly(&tm, USE_XSD_DATES, buf);
- }
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, DATEOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMP:
- {
- Timestamp timestamp;
- struct pg_tm tm;
- fsec_t fsec;
- char buf[MAXDATELEN + 1];
-
- timestamp = DatumGetTimestamp(val);
- /* Same as timestamp_out(), but forcing DateStyle */
- if (TIMESTAMP_NOT_FINITE(timestamp))
- EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
- EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf);
- else
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_TIMESTAMPTZ:
- {
- TimestampTz timestamp;
- struct pg_tm tm;
- int tz;
- fsec_t fsec;
- const char *tzn = NULL;
- char buf[MAXDATELEN + 1];
-
- timestamp = DatumGetTimestampTz(val);
- /* 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)
- EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
- else
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
- jb.type = jbvString;
- jb.val.string.len = strlen(buf);
- jb.val.string.val = pstrdup(buf);
- }
+ jb.type = jbvString;
+ jb.val.string.val = JsonEncodeDateTime(NULL, val, TIMESTAMPTZOID);
+ jb.val.string.len = strlen(jb.val.string.val);
break;
case JSONBTYPE_JSONCAST:
case JSONBTYPE_JSON:
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index 2749592..c9eb23a 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -73,5 +73,7 @@ extern void EncodeSpecialDate(DateADT dt, char *str);
extern DateADT GetSQLCurrentDate(void);
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);
#endif /* DATE_H */
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index d6baea5..e39572e 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -147,4 +147,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);
+
#endif /* JSONAPI_H */
0002-strict-do_to_timestamp-v08.patch (text/x-patch, 3.8K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index b8bd4ca..c1a63c99 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -967,7 +967,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);
@@ -984,7 +985,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);
@@ -2980,13 +2981,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;
@@ -3268,6 +3271,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "date time format")));
+ }
}
/* select a DCHCacheEntry to hold the given format picture */
@@ -3569,7 +3589,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)
@@ -3604,7 +3624,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))
@@ -3637,9 +3657,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;
@@ -3693,7 +3715,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)
0003-pass-cstring-to-do_to_timestamp-v08.patch (text/x-patch, 2.7K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index c1a63c99..f68bb20 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -985,8 +985,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);
@@ -3589,7 +3589,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)
@@ -3624,7 +3625,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))
@@ -3661,12 +3663,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;
@@ -3677,15 +3679,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)
{
/*
@@ -3717,11 +3719,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);
/*
0004-add-to_datetime-v08.patch (text/x-patch, 12.4K) download
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 747ef49..2c7a85a 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
#endif
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
/* common code for timetypmodin and timetztypmodin */
static int32
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1232,7 +1227,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)
@@ -1398,7 +1393,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] = {
@@ -1937,7 +1932,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 f68bb20..bcf079a 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -953,6 +954,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
@@ -986,7 +991,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);
@@ -3290,6 +3295,103 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
}
}
+/* Get mask of date/time/zone formatting components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3590,7 +3692,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)
@@ -3626,7 +3728,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))
@@ -3648,6 +3750,155 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
@@ -3659,12 +3910,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;
@@ -3719,6 +3974,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 e6a1eed..d631387 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 c9eb23a..c44f911 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -17,6 +17,7 @@
#include <math.h>
#include "fmgr.h"
+#include "utils/timestamp.h"
typedef int32 DateADT;
@@ -75,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 d66582b..d3dd851 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 a9f5548..208cc00 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
0005-jsonpath-v08.patch (text/x-patch, 210.8K) download
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9aa9b28..0011769 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -149,6 +149,12 @@
</row>
<row>
+ <entry><type>jsonpath</type></entry>
+ <entry></entry>
+ <entry>binary JSON path</entry>
+ </row>
+
+ <row>
<entry><type>line</type></entry>
<entry></entry>
<entry>infinite line on a plane</entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2428434..61d0624 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11064,6 +11064,7 @@ table2-mapping
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11073,6 +11074,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11081,6 +11083,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11088,6 +11091,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11095,6 +11099,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11102,6 +11107,7 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object at specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
@@ -11109,10 +11115,39 @@ table2-mapping
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object at specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Does JSON path return any item for a specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..f179bc4 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,16 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
compared using the default database collation.
</para>
</sect2>
+
+ <sect2 id="json-path">
+ <title><type>jsonpath</type></title>
+ <indexterm>
+ <primary>json</primary>
+ <secondary>path</secondary>
+ </indexterm>
+
+ <para>
+ TODO
+ </para>
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267..23419cd 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -139,6 +139,9 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
utils/errcodes.h: utils/generate-errcodes.pl utils/errcodes.txt
$(MAKE) -C utils errcodes.h
+utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y
+ $(MAKE) -C utils/adt jsonpath_gram.h
+
# see explanation in parser/Makefile
utils/fmgrprotos.h: utils/fmgroids.h ;
@@ -169,7 +172,7 @@ submake-schemapg:
.PHONY: generated-headers
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h $(top_builddir)/src/include/utils/jsonpath_gram.h
$(top_builddir)/src/include/parser/gram.h: parser/gram.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -186,6 +189,11 @@ $(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 $<)" .
+
$(top_builddir)/src/include/utils/errcodes.h: utils/errcodes.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
cd '$(dir $@)' && rm -f $(notdir $@) && \
@@ -220,6 +228,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h
$(MAKE) -C utils fmgrtab.c fmgroids.h fmgrprotos.h errcodes.h
+ $(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
@@ -308,6 +317,7 @@ endif
clean:
rm -f $(LOCALOBJS) postgres$(X) $(POSTGRES_IMP) \
$(top_builddir)/src/include/parser/gram.h \
+ $(top_builddir)/src/include/utils/jsonpath_gram.h \
$(top_builddir)/src/include/catalog/schemapg.h \
$(top_builddir)/src/include/storage/lwlocknames.h \
$(top_builddir)/src/include/utils/fmgroids.h \
@@ -344,6 +354,7 @@ maintainer-clean: distclean
utils/fmgrtab.c \
utils/errcodes.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 798a823..da0c098 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 1fb0184..5f0b254 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -31,6 +32,26 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution
+# tarball, so they are not cleaned here.
+clean distclean maintainer-clean:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 0f70180..cd2716b 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1845,3 +1845,27 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(out);
}
+
+/*
+ * Extract scalar value from raw-scalar pseudo-array jsonb.
+ */
+JsonbValue *
+JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
+{
+ JsonbIterator *it = JsonbIteratorInit(jbc);
+ JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
+ JsonbValue tmp;
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+ Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);
+
+ tok = JsonbIteratorNext(&it, res, true);
+ Assert (tok == WJB_ELEM);
+ Assert(IsAJsonbScalar(res));
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert (tok == WJB_END_ARRAY);
+
+ return res;
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 2524584..7338178 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/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..1d51d8b
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,866 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ bool allowCurrent, bool insideArraySubscript)
+{
+ /* position from begining of jsonpath data */
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ int32 chld, next;
+
+ check_stack_depth();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ {
+ 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));
+
+ chld = flattenJsonPathParseItem(buf, item->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld;
+ chld = flattenJsonPathParseItem(buf, item->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ *(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,
+ allowCurrent,
+ insideArraySubscript);
+ *(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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ item->type == jpiFilter ||
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (!allowCurrent)
+ ereport(ERROR,
+ (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;
+ 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, true);
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ true, true);
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ *(int32*)(buf->data + next) =
+ flattenJsonPathParseItem(buf, item->next, allowCurrent,
+ insideArraySubscript);
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, false, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ 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);
+ else
+ 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(", 10);
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+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:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ case jpiDatetime:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus ||
+ v->type == jpiDatetime
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..df19db1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2616 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+typedef struct JsonPathExecContext
+{
+ List *vars;
+ bool lax;
+ JsonbValue *root; /* for $ evaluation */
+ int innermostArraySize; /* for LAST array index evaluation */
+} JsonPathExecContext;
+
+typedef struct JsonValueListIterator
+{
+ ListCell *lcell;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
+
+
+static inline void
+JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+{
+ if (jvl->singleton)
+ {
+ jvl->list = list_make2(jvl->singleton, jbv);
+ jvl->singleton = NULL;
+ }
+ else if (!jvl->list)
+ jvl->singleton = jbv;
+ else
+ jvl->list = lappend(jvl->list, jbv);
+}
+
+static inline void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (jvl1->singleton)
+ {
+ if (jvl2.singleton)
+ jvl1->list = list_make2(jvl1->singleton, jvl2.singleton);
+ else
+ jvl1->list = lcons(jvl1->singleton, jvl2.list);
+
+ jvl1->singleton = NULL;
+ }
+ else if (jvl2.singleton)
+ {
+ if (jvl1->list)
+ jvl1->list = lappend(jvl1->list, jvl2.singleton);
+ else
+ jvl1->singleton = jvl2.singleton;
+ }
+ else if (jvl1->list)
+ jvl1->list = list_concat(jvl1->list, jvl2.list);
+ else
+ jvl1->list = jvl2.list;
+}
+
+static inline int
+JsonValueListLength(const JsonValueList *jvl)
+{
+ return jvl->singleton ? 1 : list_length(jvl->list);
+}
+
+static inline bool
+JsonValueListIsEmpty(JsonValueList *jvl)
+{
+ return !jvl->singleton && list_length(jvl->list) <= 0;
+}
+
+static inline JsonbValue *
+JsonValueListHead(JsonValueList *jvl)
+{
+ return jvl->singleton ? jvl->singleton : linitial(jvl->list);
+}
+
+static inline void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static inline List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ if (jvl->singleton)
+ return list_make1(jvl->singleton);
+
+ return jvl->list;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static void
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ foreach(cell, vars)
+ {
+ var = (JsonPathVariable*)lfirst(cell);
+
+ if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) &&
+ !strncmp(varName, VARDATA_ANY(var->varName), varNameLength))
+ break;
+
+ var = NULL;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return;
+ }
+
+ switch(var->typid)
+ {
+ case BOOLOID:
+ value->type = jbvBool;
+ value->val.boolean = DatumGetBool(computedValue);
+ break;
+ case NUMERICOID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(computedValue);
+ break;
+ break;
+ case INT2OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int2_numeric, computedValue));
+ break;
+ case INT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int4_numeric, computedValue));
+ break;
+ case INT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric, computedValue));
+ break;
+ case FLOAT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case FLOAT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ value->type = jbvString;
+ value->val.string.val = VARDATA_ANY(computedValue);
+ value->val.string.len = VARSIZE_ANY_EXHDR(computedValue);
+ break;
+ case 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);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static void
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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, cxt->vars, value);
+ break;
+ default:
+ elog(ERROR, "Wrong type");
+ }
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathExecResult
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jperOk : jperNotFound;
+
+ return jperError;
+ }
+
+ 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;
+ 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;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jperError;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jperOk : jperNotFound;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathExecResult
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jperError;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jperNotFound;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jperError;
+ }
+
+ return res ? jperOk : jperNotFound;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (cxt->lax)
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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 satifistfying
+ * condition. In strict mode all pairs should be comparable, otherwise an error
+ * is returned.
+ */
+static JsonPathExecResult
+executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperError;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ 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 (cxt->lax)
+ return jperOk;
+
+ found = true;
+ }
+ else if (res == jperError)
+ {
+ if (!cxt->lax)
+ return jperError;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jperOk;
+
+ if (error) /* possible only in lax mode */
+ return jperError;
+
+ return jperNotFound;
+}
+
+/*
+ * 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ 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);
+
+ return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false);
+}
+
+/*
+ * Execute unary arithemitc 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursivly iterate over jsonb objects/arrays
+ */
+ 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 */
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathExecResult
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ 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 (JsonValueListLength(&rseq) != 1)
+ return jperError;
+
+ initial = JsonValueListHead(&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;
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ 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;
+}
+
+static JsonPathExecResult
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ 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;
+}
+
+/*
+ * Try to parse datetime text with the specified datetime template.
+ * Returns 'value' datum, its type 'typid' and 'typmod'.
+ */
+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;
+}
+
+/*
+ * Convert execution status 'res' to a boolean JSON item and execute next
+ * jsonpath if 'needBool' is false:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => null (errors are converted to NULL values)
+ */
+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; /* simply return status */
+ }
+
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found, bool needBool)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+
+ check_stack_depth();
+
+ switch(jsp->type) {
+ 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 jperOk:
+ res = jperNotFound;
+ break;
+ case jperNotFound:
+ res = jperOk;
+ break;
+ default:
+ 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)
+ {
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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)
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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;
+ }
+
+ JsonbExtractScalar(jb->val.binary.data, v);
+ }
+ else
+ v = jb;
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+ break;
+ }
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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);
+ break;
+
+ case jpiIndexArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonbArraySize(jb);
+ bool binary = jb->type == jbvBinary;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!cxt->lax &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ 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;
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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);
+
+ if (cxt->lax)
+ res = recursiveExecute(cxt, &elem, jb, NULL);
+ else
+ {
+ 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;
+ }
+
+ res = appendBoolResult(cxt, jsp, found, res, needBool);
+ break;
+ case jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ {
+ 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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ 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;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ 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);
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ 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);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ for (; elem < last; elem++)
+ {
+ res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
+
+ 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, false);
+ if (jperIsError(res))
+ break;
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+
+ 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)
+{
+ if (cxt->lax && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+static inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (cxt->lax)
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* 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:
+ case jpiIndexArray:
+ jb = wrapItem(jb);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Execute boolean-valued jsonpath expression. Boolean items are not appended
+ * to the result list, only return code determines result:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => NULL (errors are converted to NULL values)
+ */
+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
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONBOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static 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)
+{
+ 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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ throwJsonPathError(res);
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)))
+ {
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..e62a0b5
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,477 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+#include "utils/jsonpath_scanner.h"
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+static JsonPathParseItem*
+makeItemType(int type)
+{
+ JsonPathParseItem* v = palloc(sizeof(*v));
+
+ v->type = type;
+ v->next = NULL;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemString(string *s)
+{
+ JsonPathParseItem *v;
+
+ if (s == NULL)
+ {
+ v = makeItemType(jpiNull);
+ }
+ else
+ {
+ v = makeItemType(jpiString);
+ v->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first > 0) ? first : 0;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
+{
+ JsonPathParseItem *v = makeItemType(jpiLikeRegex);
+ int i;
+
+ 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;
+ break;
+ case 's':
+ v->value.like_regex.flags &= ~JSP_REGEX_MLINE;
+ v->value.like_regex.flags |= JSP_REGEX_SLINE;
+ break;
+ case 'm':
+ v->value.like_regex.flags &= ~JSP_REGEX_SLINE;
+ v->value.like_regex.flags |= JSP_REGEX_MLINE;
+ break;
+ case 'x':
+ v->value.like_regex.flags |= JSP_REGEX_WSPACE;
+ break;
+ default:
+ yyerror(NULL, "unrecognized flag of LIKE_REGEX predicate");
+ break;
+ }
+ }
+
+ return v;
+}
+
+%}
+
+/* BISON Declarations */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> 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 <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | pexpr 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
+ { $$ = makeItemLikeRegex($1, &$3, &$5); };
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+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); }
+ ;
+
+index_elem:
+ pexpr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | pexpr TO_P pexpr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+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)); }
+ ;
+
+accessor_op:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' 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); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..aad4aa2
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,557 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 xVARQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
+blank [ \t\n\r\f]
+unicode \\u[0-9A-Fa-f]{4}
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\") {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\[\"\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 5025a44..a8857a5 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -335,7 +335,7 @@ RE_execute(regex_t *re, char *dat, int dat_len,
* Both pattern and data are given in the database encoding. We internally
* convert to array of pg_wchar which is what Spencer's regex package wants.
*/
-static bool
+bool
RE_compile_and_execute(text *text_re, char *dat, int dat_len,
int cflags, Oid collation,
int nmatch, regmatch_t *pmatch)
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 1475bfe..a261bd9 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -205,6 +205,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
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e74f963..4b9adb8 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1853,5 +1853,11 @@ DATA(insert OID = 3286 ( "-" PGNSP PGUID b f f 3802 23 3802 0 0 3303 - - ));
DESCR("delete array element");
DATA(insert OID = 3287 ( "#-" PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_delete_path - - ));
DESCR("delete path");
+DATA(insert OID = 6075 ( "@*" PGNSP PGUID b f f 3802 6050 3802 0 0 6055 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 298e0ae..721af88 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5531,6 +5531,24 @@ DESCR("list of files in the WAL directory");
DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
DESCR("hash partition CHECK constraint");
+/* jsonpath */
+DATA(insert OID = 6052 ( jsonpath_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 6050 "2275" _null_ _null_ _null_ _null_ _null_ jsonpath_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6053 ( jsonpath_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6050" _null_ _null_ _null_ _null_ _null_ jsonpath_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6054 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6055 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 3802 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6056 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6057 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 3802 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath object test");
+DATA(insert OID = 6073 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 5b5b121..5103ef9 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -638,6 +638,12 @@ DESCR("Binary JSON");
#define JSONBOID 3802
DATA(insert OID = 3807 ( _jsonb PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+/* jsonpath */
+DATA(insert OID = 6050 ( jsonpath PGNSP PGUID -1 f b U f t \054 0 0 6051 jsonpath_in jsonpath_out - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("JSON Path");
+#define JSONPATHOID 6050
+DATA(insert OID = 6051 ( _jsonpath PGNSP PGUID -1 f b A f t \054 0 6050 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+
DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
DESCR("txid snapshot");
DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..8a2bf03 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -173,4 +173,8 @@ 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 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/jsonb.h b/src/include/utils/jsonb.h
index 27873d4..58b9900 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;
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +281,19 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +391,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 0000000..d06bb14
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,270 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+extern JsonPathParseResult* parsejsonpath(const char *str, int len);
+
+/*
+ * Evaluation of jsonpath
+ */
+
+typedef enum JsonPathExecStatus
+{
+ 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)
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **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 0000000..c57255c
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1630 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$.[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$.[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $.[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$.[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$.[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $.[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?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,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(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"}}
+(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"}}
+(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"}}
+(3 rows)
+
+select jsonb 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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 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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+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 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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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 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"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+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"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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 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"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+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"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..7b20b8a
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,784 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$[*]'::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 '$.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 '$+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 ? ($.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."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
+--------------------
+ $."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 '$.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;
+ ^
+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
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ jsonpath
+--------------------------
+ $?(@ starts with $"var")
+(1 row)
+
+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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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;
+ 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 e224977..096d32d 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 9fc5f1a..3c4c4a6 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -158,6 +158,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 0000000..0078c60
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,366 @@
+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]' @? '$.[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)');
+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}';
+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
+ 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);
+
+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 jsonb '{"a": [2]}' @* 'lax $.a * 3';
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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 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 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 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 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 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 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 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';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..14d11dc
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,142 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+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;
+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 '$+1'::jsonpath;
+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;
+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 ? (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;
+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;
+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;
+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;
+
+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 '$ < 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;
+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;
0006-jsonpath-gin-v08.patch (text/x-patch, 37.7K) download
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index f179bc4..1be32b7 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -536,6 +536,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index c8a2745..d978e5f 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,42 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+typedef enum { eOr, eAnd, eEntry } JsonPathNodeType;
+
+typedef struct JsonPathNode
+{
+ JsonPathNodeType type;
+ union
+ {
+ int nargs;
+ int entry;
+ } val;
+ struct JsonPathNode *args[FLEXIBLE_ARRAY_MEMBER];
+} JsonPathNode;
+
+typedef struct JsonPathExtractionContext
+{
+ Datum *entries;
+ int32 nentries;
+ int32 nallocated;
+ void *(*addKey)(void *path, char *key, int len);
+ bool pathOps;
+ bool lax;
+} JsonPathExtractionContext;
+
+typedef struct JsonPathContext
+{
+ void *path;
+ JsonPathItemType last;
+} JsonPathContext;
+
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_recursive(
+ JsonPathExtractionContext *cxt, JsonPathItem *jsp, bool not,
+ JsonPathContext path);
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -119,6 +154,436 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entries);
}
+/*
+ * Extract JSON path into the 'pathcxt' with filters.
+ * Returns true iff this path is supported by the index opclass.
+ */
+static bool
+gin_extract_jsonpath_path(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ JsonPathContext *pathcxt, List **filters)
+{
+ JsonPathItem next;
+
+ for (;;)
+ {
+ /* save the type of the last item in the path */
+ if (jsp->type != jpiFilter && jsp->type != jpiCurrent)
+ pathcxt->last = jsp->type;
+
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ pathcxt->path = NULL;
+ break;
+
+ case jpiCurrent:
+ break;
+
+ case jpiKey:
+ pathcxt->path = cxt->addKey(pathcxt->path,
+ jsp->content.value.data,
+ jsp->content.value.datalen);
+ break;
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ break;
+
+ case jpiAny:
+ case jpiAnyKey:
+ if (cxt->pathOps)
+ /* jsonb_path_ops doesn't support wildcard paths */
+ return false;
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ JsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = gin_extract_jsonpath_expr_recursive(cxt, &arg, false, *pathcxt);
+
+ 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(JsonPathExtractionContext *cxt, Datum entry)
+{
+ JsonPathNode *node = palloc(offsetof(JsonPathNode, args));
+
+ if (cxt->nentries >= cxt->nallocated)
+ {
+ if (cxt->entries)
+ {
+ cxt->nallocated *= 2;
+ cxt->entries = repalloc(cxt->entries,
+ sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ else
+ {
+ cxt->nallocated = 8;
+ cxt->entries = palloc(sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ }
+
+ node->type = eEntry;
+ node->val.entry = cxt->nentries;
+
+ cxt->entries[cxt->nentries++] = entry;
+
+ return node;
+}
+
+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_from_list(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;
+}
+
+/*
+ * 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,
+ JsonPathContext pathcxt, JsonbValue *scalar)
+{
+ JsonPathNode *node;
+ List *filters = NIL;
+ ListCell *lc;
+
+ if (!gin_extract_jsonpath_path(cxt, jsp, &pathcxt, &filters))
+ return NULL;
+
+ if (cxt->pathOps)
+ {
+ if (scalar)
+ {
+ uint32 hash = (uint32)(uintptr_t) pathcxt.path;
+
+ JsonbHashScalarValue(scalar, &hash);
+ node = gin_jsonpath_make_entry_node(cxt, UInt32GetDatum(hash));
+ }
+ else
+ node = NULL; /* jsonb_path_ops doesn't support EXISTS queries */
+ }
+ else
+ {
+ List *entries = pathcxt.path;
+ List *nodes = NIL;
+
+ if (scalar)
+ {
+ bool lastIsArrayAccessor =
+ pathcxt.last == jpiIndexArray ||
+ pathcxt.last == jpiAnyArray ? GIN_TRUE :
+ pathcxt.last == 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(eOr, 2);
+ node->args[0] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, true));
+ node->args[1] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, false));
+ }
+ else
+ {
+ Datum entry = make_scalar_key(scalar,
+ scalar->type == jbvString &&
+ lastIsArrayAccessor == GIN_TRUE);
+
+ node = gin_jsonpath_make_entry_node(cxt, entry);
+ }
+
+ nodes = lappend(nodes, node);
+ }
+
+ foreach(lc, entries)
+ nodes = lappend(nodes, gin_jsonpath_make_entry_node(cxt,
+ PointerGetDatum(lfirst(lc))));
+
+ if (list_length(nodes) > 0)
+ node = gin_jsonpath_make_expr_node_from_list(eAnd, nodes);
+ else
+ node = NULL; /* need full scan for EXISTS($) queries */
+ }
+
+ if (list_length(filters) <= 0)
+ return node;
+
+ /* construct AND-node for path with filters */
+ if (node)
+ filters = lcons(node, filters);
+
+ return gin_jsonpath_make_expr_node_from_list(eAnd, filters);
+}
+
+/* Extract nodes from the boolean jsonpath expression. */
+static JsonPathNode *
+gin_extract_jsonpath_expr_recursive(JsonPathExtractionContext *cxt,
+ JsonPathItem *jsp, bool not,
+ JsonPathContext path)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd:
+ case jpiOr:
+ {
+ JsonPathItem arg;
+ JsonPathNode *larg;
+ JsonPathNode *rarg;
+ JsonPathNode *node;
+ JsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? eAnd : eOr;
+ node = gin_jsonpath_make_expr_node(type, 2);
+ node->args[0] = larg;
+ node->args[1] = rarg;
+
+ return node;
+ }
+
+ case jpiNot:
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ return gin_extract_jsonpath_expr_recursive(cxt, &arg, !not, path);
+ }
+
+ case jpiExists:
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return false;
+
+ 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;
+ }
+}
+
+/* Append key name to a path. */
+static void *
+gin_jsonb_ops_add_key(void *path, char *key, int len)
+{
+ return lappend((List *) path, DatumGetPointer(
+ make_text_key(JGINFLAG_KEY, key, len)));
+}
+
+/* Combine existing path hash with next key hash. */
+static void *
+gin_jsonb_path_ops_add_key(void *path, char *key, int len)
+{
+ JsonbValue jbv;
+ uint32 hash = (uint32)(uintptr_t) path;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = key;
+ jbv.val.string.len = len;
+
+ JsonbHashScalarValue(&jbv, &hash);
+
+ return (void *)(uintptr_t) hash;
+}
+
+static Datum *
+gin_extract_jsonpath_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ JsonPathExtractionContext cxt = { 0 };
+ JsonPathItem root;
+ JsonPathNode *node;
+ JsonPathContext path = { NULL, GIN_FALSE };
+
+ cxt.addKey = pathOps ? gin_jsonb_path_ops_add_key : gin_jsonb_ops_add_key;
+ cxt.pathOps = pathOps;
+ cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? gin_extract_jsonpath_node(&cxt, &root, path, NULL)
+ : gin_extract_jsonpath_expr_recursive(&cxt, &root, false, path);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ *nentries = cxt.nentries;
+ *extra_data = palloc(sizeof(**extra_data) * cxt.nentries);
+ **extra_data = (Pointer) node;
+
+ return cxt.entries;
+}
+
+static GinTernaryValue
+gin_execute_jsonpath(JsonPathNode *node, GinTernaryValue *check)
+{
+ 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);
+ 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);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eEntry:
+ return check[node->val.entry] ? GIN_MAYBE : GIN_FALSE;
+
+ default:
+ elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+ return GIN_FALSE;
+ }
+}
+
Datum
gin_extract_jsonb_query(PG_FUNCTION_ARGS)
{
@@ -181,6 +646,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 +676,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 +733,13 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check) != GIN_FALSE;
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +754,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 +791,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);
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -432,18 +921,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 +962,40 @@ 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)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1008,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);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_GIN_TERNARY_VALUE(res);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 03af581..e05ca03 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -821,11 +821,15 @@ DATA(insert ( 4036 3802 3802 7 s 3246 2742 0 ));
DATA(insert ( 4036 3802 25 9 s 3247 2742 0 ));
DATA(insert ( 4036 3802 1009 10 s 3248 2742 0 ));
DATA(insert ( 4036 3802 1009 11 s 3249 2742 0 ));
+DATA(insert ( 4036 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4036 3802 6050 16 s 6107 2742 0 ));
/*
* GIN jsonb_path_ops
*/
DATA(insert ( 4037 3802 3802 7 s 3246 2742 0 ));
+DATA(insert ( 4037 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4037 3802 6050 16 s 6107 2742 0 ));
/*
* SP-GiST range_ops
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 58b9900..0974ec8 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 d06bb14..87dae0d 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 465195a..178e06f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2708,6 +2708,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
@@ -2783,6 +2891,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ QUERY PLAN
+-----------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @~ '($."wait" == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @~ '($."wait" == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -2933,6 +3231,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
1012
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
RESET enable_seqscan;
DROP INDEX jidx;
-- nested tests
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f2..db0bab2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1817,6 +1817,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1880,7 +1882,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(123 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 903e5ef..b153e62 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -733,6 +733,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
@@ -751,6 +769,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -800,6 +851,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
-- exercise GIN_SEARCH_MODE_ALL
SELECT count(*) FROM testjsonb WHERE j @> '{}';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
RESET enable_seqscan;
DROP INDEX jidx;
0007-jsonpath-json-v08.patch (text/x-patch, 92.9K) download
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5f0b254..b263d88 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
@@ -46,6 +46,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 97a5b85..f7d487d 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;
}
@@ -2553,3 +2558,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 7338178..ef17817 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 @@ recurse:
/* 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)
@@ -1778,7 +1775,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;
@@ -1842,6 +1839,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 df19db1..6b7367f 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -23,6 +23,12 @@
#include "utils/jsonpath.h"
#include "utils/varlena.h"
+#ifdef JSONPATH_JSON_C
+#define JSONXOID JSONOID
+#else
+#define JSONXOID JSONBOID
+#endif
+
typedef struct JsonPathExecContext
{
List *vars;
@@ -155,6 +161,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
return lfirst(it->lcell);
}
+#ifndef JSONPATH_JSON_C
/*
* Initialize a binary JsonbValue with the given jsonb container.
*/
@@ -167,6 +174,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
return jbv;
}
+#endif
/*
* Transform a JsonbValue into a binary JsonbValue by encoding it to a
@@ -278,7 +286,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
value->val.datetime.typmod = var->typmod;
value->val.datetime.value = computedValue;
break;
- case JSONBOID:
+ case JSONXOID:
{
Jsonb *jb = DatumGetJsonbP(computedValue);
@@ -344,7 +352,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;
@@ -369,7 +377,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);
@@ -430,7 +438,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);
@@ -2372,7 +2380,7 @@ makePassingVars(Jsonb *jb)
jpv->cb_arg = v.val.numeric;
break;
case jbvBinary:
- jpv->typid = JSONBOID;
+ jpv->typid = JSONXOID;
jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
break;
default:
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..720af51
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_json.c
@@ -0,0 +1,20 @@
+#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
+
+#include "jsonpath_exec.c"
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 4b9adb8..0e5b6e7 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1859,5 +1859,11 @@ DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contse
DESCR("jsonpath exists");
DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
DESCR("jsonpath predicate");
+DATA(insert OID = 6070 ( "@*" PGNSP PGUID b f f 114 6050 114 0 0 6044 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6071 ( "@?" PGNSP PGUID b f f 114 6050 16 0 0 6043 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6108 ( "@~" PGNSP PGUID b f f 114 6050 16 0 0 6049 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 721af88..9b085ee 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5549,6 +5549,19 @@ DESCR("implementation of @~ operator");
DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
DESCR("jsonpath predicate test");
+DATA(insert OID = 6043 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6044 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 114 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6045 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6046 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 114 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath query");
+DATA(insert OID = 6049 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6069 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index e39572e..9a8d77f 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
@@ -149,4 +192,22 @@ extern text *transform_json_string_values(text *json, void *action_state,
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+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 0974ec8..5498b8a 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -224,10 +224,10 @@ typedef struct
} Jsonb;
/* convenience macros for accessing the root container in a Jsonb datum */
-#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK)
-#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0)
-#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0)
-#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0)
+#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root)
+#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root)
+#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root)
+#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root)
enum jbvType
@@ -276,6 +276,8 @@ struct JsonbValue
struct
{
int nPairs; /* 1 pair, 2 elements */
+ bool uniquified; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
JsonbPair *pairs;
} object; /* Associative container type */
@@ -370,6 +372,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,
@@ -378,6 +382,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 0000000..ee59344
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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))
+
+
+#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
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..5200847
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1649 @@
+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]';
+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]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @? 'strict $.[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? 'strict $.[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @? 'strict $.[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $.[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $.[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$.[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$.[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$.[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$.[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}';
+ ?column?
+----------
+ {"b": 1}
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b": 1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": 1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select json '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select json '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select json '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select json '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select json '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select json '[]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonpath_predicate
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select json 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select json '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()';
+ ?column?
+----------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select json '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()';
+ERROR: SQL/JSON object not found
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:14:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:54:00+00:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:14:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:54:00+10:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select json '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T00:00:00+00:00"
+(3 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-09T21:02:03+00:00"
+(2 rows)
+
+-- time comparison
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:35:00+00:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:36:00+00:00"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:34:00+00:00"
+ "2017-03-10T10:35:00+00:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
+ ?column?
+----------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+ ?column?
+----------
+(0 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 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 096d32d..4ad1e95 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 3c4c4a6..86d445e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -159,6 +159,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 0000000..fe35741
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,359 @@
+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]' @? '$.[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 '{}' @? '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)';
+
+-- 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 > 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';
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-16 00:24 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-16 00:24 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
On 01/10/2018 05:42 PM, Nikita Glukhov wrote:
> Attached new 8th version of jsonpath related patches. Complete
> documentation is still missing.
>
> The first 4 small patches are necessary datetime handling in jsonpath:
> 1. simple refactoring, extracted function that will be used later in
> jsonpath
> 2. throw an error when the input or format string contains trailing
> elements
> 3. avoid unnecessary cstring to text conversions
> 4. add function for automatic datetime type recognition by the
> presence of formatting components
>
> Should they be posted in a separate thread?
>
The first of these refactors the json/jsonb timestamp formatting into a
single function, removing a lot of code duplication. The involves
exposing time2tm() and timetz2tm(). I don't think that's a tragedy, so
unless there is any objection I propose to commit it shortly.
The next three expose a bit more of the date/time API. I'm still
reviewing those.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-17 21:01 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-17 21:01 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
On 01/15/2018 07:24 PM, Andrew Dunstan wrote:
>
> On 01/10/2018 05:42 PM, Nikita Glukhov wrote:
>> Attached new 8th version of jsonpath related patches. Complete
>> documentation is still missing.
>>
>> The first 4 small patches are necessary datetime handling in jsonpath:
>> 1. simple refactoring, extracted function that will be used later in
>> jsonpath
>> 2. throw an error when the input or format string contains trailing
>> elements
>> 3. avoid unnecessary cstring to text conversions
>> 4. add function for automatic datetime type recognition by the
>> presence of formatting components
>>
>> Should they be posted in a separate thread?
>>
>
> The first of these refactors the json/jsonb timestamp formatting into a
> single function, removing a lot of code duplication. The involves
> exposing time2tm() and timetz2tm(). I don't think that's a tragedy, so
> unless there is any objection I propose to commit it shortly.
>
> The next three expose a bit more of the date/time API. I'm still
> reviewing those.
>
I have committed the first of these patches.
I have reviewed the next three, and I think they are generally good.
There is no real point in committing them ahead of the jsonpath patch
since there would be no point in having them at all but for that patch.
Note that these do export the following hitherto internal bits of the
datetime functionality:
tm2time
tm2timetz
AdjustTimeForTypmod
AdjustTimestampForTypmod
Moving on to review the main jsonpath patch.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-22 22:41 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Andrew Dunstan @ 2018-01-22 22:41 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
On 01/17/2018 04:01 PM, Andrew Dunstan wrote:
>
> On 01/15/2018 07:24 PM, Andrew Dunstan wrote:
>> On 01/10/2018 05:42 PM, Nikita Glukhov wrote:
>>> Attached new 8th version of jsonpath related patches. Complete
>>> documentation is still missing.
>>>
>>> The first 4 small patches are necessary datetime handling in jsonpath:
>>> 1. simple refactoring, extracted function that will be used later in
>>> jsonpath
>>> 2. throw an error when the input or format string contains trailing
>>> elements
>>> 3. avoid unnecessary cstring to text conversions
>>> 4. add function for automatic datetime type recognition by the
>>> presence of formatting components
>>>
>>> Should they be posted in a separate thread?
>>>
>> The first of these refactors the json/jsonb timestamp formatting into a
>> single function, removing a lot of code duplication. The involves
>> exposing time2tm() and timetz2tm(). I don't think that's a tragedy, so
>> unless there is any objection I propose to commit it shortly.
>>
>> The next three expose a bit more of the date/time API. I'm still
>> reviewing those.
>>
>
> I have committed the first of these patches.
>
> I have reviewed the next three, and I think they are generally good.
> There is no real point in committing them ahead of the jsonpath patch
> since there would be no point in having them at all but for that patch.
>
> Note that these do export the following hitherto internal bits of the
> datetime functionality:
>
> tm2time
> tm2timetz
> AdjustTimeForTypmod
> AdjustTimestampForTypmod
>
> Moving on to review the main jsonpath patch.
>
OK, I understand a good deal more than I did about how the jsopnpath
code works, but the commenting is abysmal.
I got quite nervous about adding a new datetime variant to JsonbValue.
However, my understanding from the code is that this will only ever be
used in an intermediate jsonpath processing result, and it won't be used
in a stored or build jsonb object. Is that right? If it is we need to
say so, and moreover we need to warn coders in the header file about the
restricted use of this variant. I'm not sure we can enforce it with an
Assert, but If we can we should. I'm not 100% sure that doing it this
way, i.e. by extending and resuing jsonbValue, is desirable, I'd like to
know some of the thinking behind the design.
The encoding of a jsonpath value into a binary string is quite nifty,
but it needs to be documented. Once I understood it better some of my
fears about parser overhead were alleviated.
The use of a function pointer inside JsonPathVariable seems unnecessary
and baroque. It would be much more straightforward to set an isnull flag
in the struct and process the value in an "if" statement accordingly,
avoiding the function pointer altogether.
I am going to be travelling for a few days, then back online for a day
or two, then gone for a week. I hope we can make progress on these
features, but this needs lots more eyeballs, reviewing code as well as
testing, and lots more responsiveness. The whole suite is enormous.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-01-23 13:18 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-01-23 13:18 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
On 23.01.2018 01:41, Andrew Dunstan wrote:
> On 01/17/2018 04:01 PM, Andrew Dunstan wrote:
>> On 01/15/2018 07:24 PM, Andrew Dunstan wrote:
>>> On 01/10/2018 05:42 PM, Nikita Glukhov wrote:
>>>> Attached new 8th version of jsonpath related patches. Complete
>>>> documentation is still missing.
>>>>
>>>> The first 4 small patches are necessary datetime handling in jsonpath:
>>>> 1. simple refactoring, extracted function that will be used later in
>>>> jsonpath
>>>> 2. throw an error when the input or format string contains trailing
>>>> elements
>>>> 3. avoid unnecessary cstring to text conversions
>>>> 4. add function for automatic datetime type recognition by the
>>>> presence of formatting components
>>>>
>>>> Should they be posted in a separate thread?
>>>>
>>> The first of these refactors the json/jsonb timestamp formatting into a
>>> single function, removing a lot of code duplication. The involves
>>> exposing time2tm() and timetz2tm(). I don't think that's a tragedy, so
>>> unless there is any objection I propose to commit it shortly.
>>>
>>> The next three expose a bit more of the date/time API. I'm still
>>> reviewing those.
>> I have committed the first of these patches.
>>
>> I have reviewed the next three, and I think they are generally good.
>> There is no real point in committing them ahead of the jsonpath patch
>> since there would be no point in having them at all but for that patch.
>>
>> Note that these do export the following hitherto internal bits of the
>> datetime functionality:
>>
>> tm2time
>> tm2timetz
>> AdjustTimeForTypmod
>> AdjustTimestampForTypmod
>>
>> Moving on to review the main jsonpath patch.
>
> OK, I understand a good deal more than I did about how the jsopnpath
> code works, but the commenting is abysmal.
Thank you for reviewing.
> I got quite nervous about adding a new datetime variant to JsonbValue.
> However, my understanding from the code is that this will only ever be
> used in an intermediate jsonpath processing result, and it won't be used
> in a stored or build jsonb object. Is that right?
Yes, you are right. Datetime JsonbValues are used only for for in-memory
representation of SQL/JSON datetime items, they converted into ordinary
JSON strings in ISO format when json/jsonb encoded into a datum.
> If it is we need to say so, and moreover we need to warn coders in the
> header file about the restricted use of this variant. I'm not sure we
> can enforce it with an Assert, but If we can we should. I'm not 100%
> sure that doing it this way, i.e. by extending and resuing jsonbValue,
> is desirable, I'd like to know some of the thinking behind the design.
Datetime support was added to our jsonpath implementation when there was
already a lot of code using plain JsonbValue. So, the simplest are most
effective solution I found was JsonbValue extension. We could also
introduce extended struct SqlJsonItem, but it seems that there will be a
lot of unnecessary conversions between SqlJsonItem and JsonbValue.
> The encoding of a jsonpath value into a binary string is quite nifty,
> but it needs to be documented. Once I understood it better some of my
> fears about parser overhead were alleviated.
> The use of a function pointer inside JsonPathVariable seems unnecessary
> and baroque. It would be much more straightforward to set an isnull flag
> in the struct and process the value in an "if" statement accordingly,
> avoiding the function pointer altogether.
Callback in JsonPathVariable is used for on-demand evaluation of
SQL/JSON PASSING parameters (see EvalJsonPathVar() from patch
0010-sqljson-v08.patch). For jsonpath itself it is really unnecessary.
> I am going to be travelling for a few days, then back online for a day
> or two, then gone for a week. I hope we can make progress on these
> features, but this needs lots more eyeballs, reviewing code as well as
> testing, and lots more responsiveness. The whole suite is enormous.
>
> cheers
>
> andrew
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-02-13 23:04 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-02-13 23:04 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; +Cc: pgsql-hackers; Oleg Bartunov <obartunov@gmail.com>
Attached 9th version of jsonpath patches rebased onto the latest master.
Jsonpath grammar for parenthesized expressions and predicates was fixed.
Documentation drafts for jsonpath written by Oleg Bartunov:
https://github.com/obartunov/sqljsondoc
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (6 files, .tar.gz)
0001-strict-do_to_timestamp-v09.patch (text/x-patch, 3.8K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index b8bd4ca..c1a63c99 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -967,7 +967,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);
@@ -984,7 +985,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);
@@ -2980,13 +2981,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;
@@ -3268,6 +3271,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "date time format")));
+ }
}
/* select a DCHCacheEntry to hold the given format picture */
@@ -3569,7 +3589,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)
@@ -3604,7 +3624,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))
@@ -3637,9 +3657,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;
@@ -3693,7 +3715,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)
0002-pass-cstring-to-do_to_timestamp-v09.patch (text/x-patch, 2.7K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index c1a63c99..f68bb20 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -985,8 +985,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);
@@ -3589,7 +3589,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)
@@ -3624,7 +3625,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))
@@ -3661,12 +3663,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;
@@ -3677,15 +3679,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)
{
/*
@@ -3717,11 +3719,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);
/*
0003-add-to_datetime-v09.patch (text/x-patch, 12.3K) download
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index eea2904..43d3470 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
#endif
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
/* common code for timetypmodin and timetztypmodin */
static int32
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1260,7 +1255,7 @@ time_in(PG_FUNCTION_ARGS)
/* tm2time()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
{
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1426,7 +1421,7 @@ time_scale(PG_FUNCTION_ARGS)
* have a fundamental tie together but rather a coincidence of
* implementation. - thomas
*/
-static void
+void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -2004,7 +1999,7 @@ time_part(PG_FUNCTION_ARGS)
/* tm2timetz()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
{
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index f68bb20..bcf079a 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -953,6 +954,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
@@ -986,7 +991,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);
@@ -3290,6 +3295,103 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
}
}
+/* Get mask of date/time/zone formatting components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3590,7 +3692,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)
@@ -3626,7 +3728,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))
@@ -3648,6 +3750,155 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
@@ -3659,12 +3910,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;
@@ -3719,6 +3974,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 103f91a..9b3344a 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 eb6d2a1..10cc822 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 d66582b..d3dd851 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 a9f5548..208cc00 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
0004-jsonpath-v09.patch (text/x-patch, 210.6K) download
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9aa9b28..0011769 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -149,6 +149,12 @@
</row>
<row>
+ <entry><type>jsonpath</type></entry>
+ <entry></entry>
+ <entry>binary JSON path</entry>
+ </row>
+
+ <row>
<entry><type>line</type></entry>
<entry></entry>
<entry>infinite line on a plane</entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 640ff09..de330f2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11064,6 +11064,7 @@ table2-mapping
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11073,6 +11074,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11081,6 +11083,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11088,6 +11091,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11095,6 +11099,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11102,6 +11107,7 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object at specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
@@ -11109,10 +11115,39 @@ table2-mapping
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object at specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Does JSON path return any item for a specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..f179bc4 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,16 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
compared using the default database collation.
</para>
</sect2>
+
+ <sect2 id="json-path">
+ <title><type>jsonpath</type></title>
+ <indexterm>
+ <primary>json</primary>
+ <secondary>path</secondary>
+ </indexterm>
+
+ <para>
+ TODO
+ </para>
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267..23419cd 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -139,6 +139,9 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
utils/errcodes.h: utils/generate-errcodes.pl utils/errcodes.txt
$(MAKE) -C utils errcodes.h
+utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y
+ $(MAKE) -C utils/adt jsonpath_gram.h
+
# see explanation in parser/Makefile
utils/fmgrprotos.h: utils/fmgroids.h ;
@@ -169,7 +172,7 @@ submake-schemapg:
.PHONY: generated-headers
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h $(top_builddir)/src/include/utils/jsonpath_gram.h
$(top_builddir)/src/include/parser/gram.h: parser/gram.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -186,6 +189,11 @@ $(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 $<)" .
+
$(top_builddir)/src/include/utils/errcodes.h: utils/errcodes.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
cd '$(dir $@)' && rm -f $(notdir $@) && \
@@ -220,6 +228,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h
$(MAKE) -C utils fmgrtab.c fmgroids.h fmgrprotos.h errcodes.h
+ $(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
@@ -308,6 +317,7 @@ endif
clean:
rm -f $(LOCALOBJS) postgres$(X) $(POSTGRES_IMP) \
$(top_builddir)/src/include/parser/gram.h \
+ $(top_builddir)/src/include/utils/jsonpath_gram.h \
$(top_builddir)/src/include/catalog/schemapg.h \
$(top_builddir)/src/include/storage/lwlocknames.h \
$(top_builddir)/src/include/utils/fmgroids.h \
@@ -344,6 +354,7 @@ maintainer-clean: distclean
utils/fmgrtab.c \
utils/errcodes.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 798a823..da0c098 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 1fb0184..5f0b254 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -31,6 +32,26 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution
+# tarball, so they are not cleaned here.
+clean distclean maintainer-clean:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index 0f70180..cd2716b 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -1845,3 +1845,27 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(out);
}
+
+/*
+ * Extract scalar value from raw-scalar pseudo-array jsonb.
+ */
+JsonbValue *
+JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
+{
+ JsonbIterator *it = JsonbIteratorInit(jbc);
+ JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
+ JsonbValue tmp;
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+ Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);
+
+ tok = JsonbIteratorNext(&it, res, true);
+ Assert (tok == WJB_ELEM);
+ Assert(IsAJsonbScalar(res));
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert (tok == WJB_END_ARRAY);
+
+ return res;
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 2524584..7338178 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/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..1d51d8b
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,866 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ bool allowCurrent, bool insideArraySubscript)
+{
+ /* position from begining of jsonpath data */
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ int32 chld, next;
+
+ check_stack_depth();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ {
+ 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));
+
+ chld = flattenJsonPathParseItem(buf, item->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld;
+ chld = flattenJsonPathParseItem(buf, item->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ *(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,
+ allowCurrent,
+ insideArraySubscript);
+ *(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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ item->type == jpiFilter ||
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (!allowCurrent)
+ ereport(ERROR,
+ (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;
+ 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, true);
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ true, true);
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ *(int32*)(buf->data + next) =
+ flattenJsonPathParseItem(buf, item->next, allowCurrent,
+ insideArraySubscript);
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, false, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ 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);
+ else
+ 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(", 10);
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+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:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ case jpiDatetime:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus ||
+ v->type == jpiDatetime
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..452bc9b
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2617 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+typedef struct JsonPathExecContext
+{
+ List *vars;
+ bool lax;
+ JsonbValue *root; /* for $ evaluation */
+ int innermostArraySize; /* for LAST array index evaluation */
+} JsonPathExecContext;
+
+typedef struct JsonValueListIterator
+{
+ ListCell *lcell;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
+
+
+static inline void
+JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+{
+ if (jvl->singleton)
+ {
+ jvl->list = list_make2(jvl->singleton, jbv);
+ jvl->singleton = NULL;
+ }
+ else if (!jvl->list)
+ jvl->singleton = jbv;
+ else
+ jvl->list = lappend(jvl->list, jbv);
+}
+
+static inline void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (jvl1->singleton)
+ {
+ if (jvl2.singleton)
+ jvl1->list = list_make2(jvl1->singleton, jvl2.singleton);
+ else
+ jvl1->list = lcons(jvl1->singleton, jvl2.list);
+
+ jvl1->singleton = NULL;
+ }
+ else if (jvl2.singleton)
+ {
+ if (jvl1->list)
+ jvl1->list = lappend(jvl1->list, jvl2.singleton);
+ else
+ jvl1->singleton = jvl2.singleton;
+ }
+ else if (jvl1->list)
+ jvl1->list = list_concat(jvl1->list, jvl2.list);
+ else
+ jvl1->list = jvl2.list;
+}
+
+static inline int
+JsonValueListLength(const JsonValueList *jvl)
+{
+ return jvl->singleton ? 1 : list_length(jvl->list);
+}
+
+static inline bool
+JsonValueListIsEmpty(JsonValueList *jvl)
+{
+ return !jvl->singleton && list_length(jvl->list) <= 0;
+}
+
+static inline JsonbValue *
+JsonValueListHead(JsonValueList *jvl)
+{
+ return jvl->singleton ? jvl->singleton : linitial(jvl->list);
+}
+
+static inline void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static inline List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ if (jvl->singleton)
+ return list_make1(jvl->singleton);
+
+ return jvl->list;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static void
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ foreach(cell, vars)
+ {
+ var = (JsonPathVariable*)lfirst(cell);
+
+ if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) &&
+ !strncmp(varName, VARDATA_ANY(var->varName), varNameLength))
+ break;
+
+ var = NULL;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return;
+ }
+
+ switch(var->typid)
+ {
+ case BOOLOID:
+ value->type = jbvBool;
+ value->val.boolean = DatumGetBool(computedValue);
+ break;
+ case NUMERICOID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(computedValue);
+ break;
+ break;
+ case INT2OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int2_numeric, computedValue));
+ break;
+ case INT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int4_numeric, computedValue));
+ break;
+ case INT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric, computedValue));
+ break;
+ case FLOAT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case FLOAT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ value->type = jbvString;
+ value->val.string.val = VARDATA_ANY(computedValue);
+ value->val.string.len = VARSIZE_ANY_EXHDR(computedValue);
+ break;
+ case 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);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static void
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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, cxt->vars, value);
+ break;
+ default:
+ elog(ERROR, "Wrong type");
+ }
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathExecResult
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jperOk : jperNotFound;
+
+ return jperError;
+ }
+
+ 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;
+ 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;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jperError;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jperOk : jperNotFound;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathExecResult
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jperError;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jperNotFound;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jperError;
+ }
+
+ return res ? jperOk : jperNotFound;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (cxt->lax)
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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 satifistfying
+ * condition. In strict mode all pairs should be comparable, otherwise an error
+ * is returned.
+ */
+static JsonPathExecResult
+executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperError;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ 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 (cxt->lax)
+ return jperOk;
+
+ found = true;
+ }
+ else if (res == jperError)
+ {
+ if (!cxt->lax)
+ return jperError;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jperOk;
+
+ if (error) /* possible only in lax mode */
+ return jperError;
+
+ return jperNotFound;
+}
+
+/*
+ * 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ 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);
+
+ return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false);
+}
+
+/*
+ * Execute unary arithemitc 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursivly iterate over jsonb objects/arrays
+ */
+ 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 */
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathExecResult
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ 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 (JsonValueListLength(&rseq) != 1)
+ return jperError;
+
+ initial = JsonValueListHead(&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;
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ 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;
+}
+
+static JsonPathExecResult
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ 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;
+}
+
+/*
+ * Try to parse datetime text with the specified datetime template.
+ * Returns 'value' datum, its type 'typid' and 'typmod'.
+ */
+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;
+}
+
+/*
+ * Convert execution status 'res' to a boolean JSON item and execute next
+ * jsonpath if 'needBool' is false:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => null (errors are converted to NULL values)
+ */
+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; /* simply return status */
+ }
+
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found, bool needBool)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+
+ check_stack_depth();
+
+ switch(jsp->type) {
+ 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 jperOk:
+ res = jperNotFound;
+ break;
+ case jperNotFound:
+ res = jperOk;
+ break;
+ default:
+ 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)
+ {
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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)
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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;
+ }
+
+ JsonbExtractScalar(jb->val.binary.data, v);
+ }
+ else
+ v = jb;
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+ break;
+ }
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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);
+ break;
+
+ case jpiIndexArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonbArraySize(jb);
+ bool binary = jb->type == jbvBinary;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!cxt->lax &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ 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;
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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);
+
+ if (cxt->lax)
+ res = recursiveExecute(cxt, &elem, jb, NULL);
+ else
+ {
+ 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;
+ }
+
+ res = appendBoolResult(cxt, jsp, found, res, needBool);
+ break;
+ case jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ {
+ 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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ 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;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ 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);
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ 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);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ for (; elem < last; elem++)
+ {
+ res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
+
+ 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, false);
+ if (jperIsError(res))
+ break;
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+
+ 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)
+{
+ if (cxt->lax && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+static inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (cxt->lax)
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* 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:
+ case jpiIndexArray:
+ jb = wrapItem(jb);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Execute boolean-valued jsonpath expression. Boolean items are not appended
+ * to the result list, only return code determines result:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => NULL (errors are converted to NULL values)
+ */
+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
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONBOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static 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)
+{
+ 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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)))
+ {
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..4339855
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,472 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "nodes/pg_list.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+#include "utils/jsonpath_scanner.h"
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+static JsonPathParseItem*
+makeItemType(int type)
+{
+ JsonPathParseItem* v = palloc(sizeof(*v));
+
+ v->type = type;
+ v->next = NULL;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemString(string *s)
+{
+ JsonPathParseItem *v;
+
+ if (s == NULL)
+ {
+ v = makeItemType(jpiNull);
+ }
+ else
+ {
+ v = makeItemType(jpiString);
+ v->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first > 0) ? first : 0;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
+{
+ JsonPathParseItem *v = makeItemType(jpiLikeRegex);
+ int i;
+
+ 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;
+ break;
+ case 's':
+ v->value.like_regex.flags &= ~JSP_REGEX_MLINE;
+ v->value.like_regex.flags |= JSP_REGEX_SLINE;
+ break;
+ case 'm':
+ v->value.like_regex.flags &= ~JSP_REGEX_SLINE;
+ v->value.like_regex.flags |= JSP_REGEX_MLINE;
+ break;
+ case 'x':
+ v->value.like_regex.flags |= JSP_REGEX_WSPACE;
+ break;
+ default:
+ yyerror(NULL, "unrecognized flag of LIKE_REGEX predicate");
+ break;
+ }
+ }
+
+ return v;
+}
+
+%}
+
+/* BISON Declarations */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> 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
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); };
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); };
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+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)); }
+ ;
+
+accessor_op:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' 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); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..aad4aa2
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,557 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 xVARQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
+blank [ \t\n\r\f]
+unicode \\u[0-9A-Fa-f]{4}
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\") {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\[\"\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 5025a44..a8857a5 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -335,7 +335,7 @@ RE_execute(regex_t *re, char *dat, int dat_len,
* Both pattern and data are given in the database encoding. We internally
* convert to array of pg_wchar which is what Spencer's regex package wants.
*/
-static bool
+bool
RE_compile_and_execute(text *text_re, char *dat, int dat_len,
int cflags, Oid collation,
int nmatch, regmatch_t *pmatch)
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 9871d1e..27948ec 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
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e74f963..4b9adb8 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1853,5 +1853,11 @@ DATA(insert OID = 3286 ( "-" PGNSP PGUID b f f 3802 23 3802 0 0 3303 - - ));
DESCR("delete array element");
DATA(insert OID = 3287 ( "#-" PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_delete_path - - ));
DESCR("delete path");
+DATA(insert OID = 6075 ( "@*" PGNSP PGUID b f f 3802 6050 3802 0 0 6055 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 2a53213..27e297e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5561,6 +5561,24 @@ DESCR("list of files in the WAL directory");
DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
DESCR("hash partition CHECK constraint");
+/* jsonpath */
+DATA(insert OID = 6052 ( jsonpath_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 6050 "2275" _null_ _null_ _null_ _null_ _null_ jsonpath_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6053 ( jsonpath_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6050" _null_ _null_ _null_ _null_ _null_ jsonpath_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6054 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6055 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 3802 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6056 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6057 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 3802 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath object test");
+DATA(insert OID = 6073 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 5b5b121..5103ef9 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -638,6 +638,12 @@ DESCR("Binary JSON");
#define JSONBOID 3802
DATA(insert OID = 3807 ( _jsonb PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+/* jsonpath */
+DATA(insert OID = 6050 ( jsonpath PGNSP PGUID -1 f b U f t \054 0 0 6051 jsonpath_in jsonpath_out - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("JSON Path");
+#define JSONPATHOID 6050
+DATA(insert OID = 6051 ( _jsonpath PGNSP PGUID -1 f b A f t \054 0 6050 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+
DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
DESCR("txid snapshot");
DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..8a2bf03 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -173,4 +173,8 @@ 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 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/jsonb.h b/src/include/utils/jsonb.h
index 27873d4..58b9900 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;
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +281,19 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +391,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 0000000..d06bb14
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,270 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+extern JsonPathParseResult* parsejsonpath(const char *str, int len);
+
+/*
+ * Evaluation of jsonpath
+ */
+
+typedef enum JsonPathExecStatus
+{
+ 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)
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **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 0000000..3102641
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1640 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?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,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(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"}}
+(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"}}
+(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"}}
+(3 rows)
+
+select jsonb 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+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 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 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 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 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 jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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 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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+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 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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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 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"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+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"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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 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"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+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"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..eb77f47
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,766 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.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 '$+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 ? ($.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ jsonpath
+--------------------------
+ $?(@ starts with $"var")
+(1 row)
+
+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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 ad9434f..20b2391 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 27cd498..93bb283 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -158,6 +158,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 0000000..044ba59
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,367 @@
+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)');
+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}';
+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
+ 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);
+
+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 jsonb '{"a": [2]}' @* 'lax $.a * 3';
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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 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 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 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 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 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 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 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';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..e068e63
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,139 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 '$+1'::jsonpath;
+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;
+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 ? (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;
+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.size() - 3]'::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;
+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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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;
0005-jsonpath-gin-v09.patch (text/x-patch, 37.7K) download
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index f179bc4..1be32b7 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -536,6 +536,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index c8a2745..d978e5f 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,42 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+typedef enum { eOr, eAnd, eEntry } JsonPathNodeType;
+
+typedef struct JsonPathNode
+{
+ JsonPathNodeType type;
+ union
+ {
+ int nargs;
+ int entry;
+ } val;
+ struct JsonPathNode *args[FLEXIBLE_ARRAY_MEMBER];
+} JsonPathNode;
+
+typedef struct JsonPathExtractionContext
+{
+ Datum *entries;
+ int32 nentries;
+ int32 nallocated;
+ void *(*addKey)(void *path, char *key, int len);
+ bool pathOps;
+ bool lax;
+} JsonPathExtractionContext;
+
+typedef struct JsonPathContext
+{
+ void *path;
+ JsonPathItemType last;
+} JsonPathContext;
+
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_recursive(
+ JsonPathExtractionContext *cxt, JsonPathItem *jsp, bool not,
+ JsonPathContext path);
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -119,6 +154,436 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(entries);
}
+/*
+ * Extract JSON path into the 'pathcxt' with filters.
+ * Returns true iff this path is supported by the index opclass.
+ */
+static bool
+gin_extract_jsonpath_path(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ JsonPathContext *pathcxt, List **filters)
+{
+ JsonPathItem next;
+
+ for (;;)
+ {
+ /* save the type of the last item in the path */
+ if (jsp->type != jpiFilter && jsp->type != jpiCurrent)
+ pathcxt->last = jsp->type;
+
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ pathcxt->path = NULL;
+ break;
+
+ case jpiCurrent:
+ break;
+
+ case jpiKey:
+ pathcxt->path = cxt->addKey(pathcxt->path,
+ jsp->content.value.data,
+ jsp->content.value.datalen);
+ break;
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ break;
+
+ case jpiAny:
+ case jpiAnyKey:
+ if (cxt->pathOps)
+ /* jsonb_path_ops doesn't support wildcard paths */
+ return false;
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ JsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = gin_extract_jsonpath_expr_recursive(cxt, &arg, false, *pathcxt);
+
+ 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(JsonPathExtractionContext *cxt, Datum entry)
+{
+ JsonPathNode *node = palloc(offsetof(JsonPathNode, args));
+
+ if (cxt->nentries >= cxt->nallocated)
+ {
+ if (cxt->entries)
+ {
+ cxt->nallocated *= 2;
+ cxt->entries = repalloc(cxt->entries,
+ sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ else
+ {
+ cxt->nallocated = 8;
+ cxt->entries = palloc(sizeof(cxt->entries[0]) * cxt->nallocated);
+ }
+ }
+
+ node->type = eEntry;
+ node->val.entry = cxt->nentries;
+
+ cxt->entries[cxt->nentries++] = entry;
+
+ return node;
+}
+
+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_from_list(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;
+}
+
+/*
+ * 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,
+ JsonPathContext pathcxt, JsonbValue *scalar)
+{
+ JsonPathNode *node;
+ List *filters = NIL;
+ ListCell *lc;
+
+ if (!gin_extract_jsonpath_path(cxt, jsp, &pathcxt, &filters))
+ return NULL;
+
+ if (cxt->pathOps)
+ {
+ if (scalar)
+ {
+ uint32 hash = (uint32)(uintptr_t) pathcxt.path;
+
+ JsonbHashScalarValue(scalar, &hash);
+ node = gin_jsonpath_make_entry_node(cxt, UInt32GetDatum(hash));
+ }
+ else
+ node = NULL; /* jsonb_path_ops doesn't support EXISTS queries */
+ }
+ else
+ {
+ List *entries = pathcxt.path;
+ List *nodes = NIL;
+
+ if (scalar)
+ {
+ bool lastIsArrayAccessor =
+ pathcxt.last == jpiIndexArray ||
+ pathcxt.last == jpiAnyArray ? GIN_TRUE :
+ pathcxt.last == 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(eOr, 2);
+ node->args[0] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, true));
+ node->args[1] = gin_jsonpath_make_entry_node(cxt,
+ make_scalar_key(scalar, false));
+ }
+ else
+ {
+ Datum entry = make_scalar_key(scalar,
+ scalar->type == jbvString &&
+ lastIsArrayAccessor == GIN_TRUE);
+
+ node = gin_jsonpath_make_entry_node(cxt, entry);
+ }
+
+ nodes = lappend(nodes, node);
+ }
+
+ foreach(lc, entries)
+ nodes = lappend(nodes, gin_jsonpath_make_entry_node(cxt,
+ PointerGetDatum(lfirst(lc))));
+
+ if (list_length(nodes) > 0)
+ node = gin_jsonpath_make_expr_node_from_list(eAnd, nodes);
+ else
+ node = NULL; /* need full scan for EXISTS($) queries */
+ }
+
+ if (list_length(filters) <= 0)
+ return node;
+
+ /* construct AND-node for path with filters */
+ if (node)
+ filters = lcons(node, filters);
+
+ return gin_jsonpath_make_expr_node_from_list(eAnd, filters);
+}
+
+/* Extract nodes from the boolean jsonpath expression. */
+static JsonPathNode *
+gin_extract_jsonpath_expr_recursive(JsonPathExtractionContext *cxt,
+ JsonPathItem *jsp, bool not,
+ JsonPathContext path)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd:
+ case jpiOr:
+ {
+ JsonPathItem arg;
+ JsonPathNode *larg;
+ JsonPathNode *rarg;
+ JsonPathNode *node;
+ JsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = gin_extract_jsonpath_expr_recursive(cxt, &arg, not, path);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? eAnd : eOr;
+ node = gin_jsonpath_make_expr_node(type, 2);
+ node->args[0] = larg;
+ node->args[1] = rarg;
+
+ return node;
+ }
+
+ case jpiNot:
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ return gin_extract_jsonpath_expr_recursive(cxt, &arg, !not, path);
+ }
+
+ case jpiExists:
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return false;
+
+ 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;
+ }
+}
+
+/* Append key name to a path. */
+static void *
+gin_jsonb_ops_add_key(void *path, char *key, int len)
+{
+ return lappend((List *) path, DatumGetPointer(
+ make_text_key(JGINFLAG_KEY, key, len)));
+}
+
+/* Combine existing path hash with next key hash. */
+static void *
+gin_jsonb_path_ops_add_key(void *path, char *key, int len)
+{
+ JsonbValue jbv;
+ uint32 hash = (uint32)(uintptr_t) path;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = key;
+ jbv.val.string.len = len;
+
+ JsonbHashScalarValue(&jbv, &hash);
+
+ return (void *)(uintptr_t) hash;
+}
+
+static Datum *
+gin_extract_jsonpath_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ JsonPathExtractionContext cxt = { 0 };
+ JsonPathItem root;
+ JsonPathNode *node;
+ JsonPathContext path = { NULL, GIN_FALSE };
+
+ cxt.addKey = pathOps ? gin_jsonb_path_ops_add_key : gin_jsonb_ops_add_key;
+ cxt.pathOps = pathOps;
+ cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? gin_extract_jsonpath_node(&cxt, &root, path, NULL)
+ : gin_extract_jsonpath_expr_recursive(&cxt, &root, false, path);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ *nentries = cxt.nentries;
+ *extra_data = palloc(sizeof(**extra_data) * cxt.nentries);
+ **extra_data = (Pointer) node;
+
+ return cxt.entries;
+}
+
+static GinTernaryValue
+gin_execute_jsonpath(JsonPathNode *node, GinTernaryValue *check)
+{
+ 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);
+ 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);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eEntry:
+ return check[node->val.entry] ? GIN_MAYBE : GIN_FALSE;
+
+ default:
+ elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+ return GIN_FALSE;
+ }
+}
+
Datum
gin_extract_jsonb_query(PG_FUNCTION_ARGS)
{
@@ -181,6 +646,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 +676,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 +733,13 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check) != GIN_FALSE;
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +754,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 +791,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);
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -432,18 +921,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 +962,40 @@ 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)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1008,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);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_GIN_TERNARY_VALUE(res);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 03af581..e05ca03 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -821,11 +821,15 @@ DATA(insert ( 4036 3802 3802 7 s 3246 2742 0 ));
DATA(insert ( 4036 3802 25 9 s 3247 2742 0 ));
DATA(insert ( 4036 3802 1009 10 s 3248 2742 0 ));
DATA(insert ( 4036 3802 1009 11 s 3249 2742 0 ));
+DATA(insert ( 4036 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4036 3802 6050 16 s 6107 2742 0 ));
/*
* GIN jsonb_path_ops
*/
DATA(insert ( 4037 3802 3802 7 s 3246 2742 0 ));
+DATA(insert ( 4037 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4037 3802 6050 16 s 6107 2742 0 ));
/*
* SP-GiST range_ops
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 58b9900..0974ec8 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 d06bb14..87dae0d 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 465195a..178e06f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2708,6 +2708,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
@@ -2783,6 +2891,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ QUERY PLAN
+-----------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @~ '($."wait" == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @~ '($."wait" == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -2933,6 +3231,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
1012
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
RESET enable_seqscan;
DROP INDEX jidx;
-- nested tests
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 684f7f2..db0bab2 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1817,6 +1817,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1880,7 +1882,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(123 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 903e5ef..b153e62 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -733,6 +733,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
@@ -751,6 +769,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -800,6 +851,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
-- exercise GIN_SEARCH_MODE_ALL
SELECT count(*) FROM testjsonb WHERE j @> '{}';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
RESET enable_seqscan;
DROP INDEX jidx;
0006-jsonpath-json-v09.patch (text/x-patch, 93.0K) download
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5f0b254..b263d88 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -16,7 +16,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
@@ -46,6 +46,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 3ba9bb3..ff8e5fc 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;
}
@@ -2553,3 +2558,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 7338178..ef17817 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 @@ recurse:
/* 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)
@@ -1778,7 +1775,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;
@@ -1842,6 +1839,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 452bc9b..e24712f 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -23,6 +23,12 @@
#include "utils/jsonpath.h"
#include "utils/varlena.h"
+#ifdef JSONPATH_JSON_C
+#define JSONXOID JSONOID
+#else
+#define JSONXOID JSONBOID
+#endif
+
typedef struct JsonPathExecContext
{
List *vars;
@@ -155,6 +161,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
return lfirst(it->lcell);
}
+#ifndef JSONPATH_JSON_C
/*
* Initialize a binary JsonbValue with the given jsonb container.
*/
@@ -167,6 +174,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
return jbv;
}
+#endif
/*
* Transform a JsonbValue into a binary JsonbValue by encoding it to a
@@ -278,7 +286,7 @@ computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
value->val.datetime.typmod = var->typmod;
value->val.datetime.value = computedValue;
break;
- case JSONBOID:
+ case JSONXOID:
{
Jsonb *jb = DatumGetJsonbP(computedValue);
@@ -344,7 +352,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;
@@ -369,7 +377,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);
@@ -430,7 +438,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);
@@ -2372,7 +2380,7 @@ makePassingVars(Jsonb *jb)
jpv->cb_arg = v.val.numeric;
break;
case jbvBinary:
- jpv->typid = JSONBOID;
+ jpv->typid = JSONXOID;
jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
break;
default:
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..720af51
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_json.c
@@ -0,0 +1,20 @@
+#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
+
+#include "jsonpath_exec.c"
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 4b9adb8..0e5b6e7 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1859,5 +1859,11 @@ DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contse
DESCR("jsonpath exists");
DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
DESCR("jsonpath predicate");
+DATA(insert OID = 6070 ( "@*" PGNSP PGUID b f f 114 6050 114 0 0 6044 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6071 ( "@?" PGNSP PGUID b f f 114 6050 16 0 0 6043 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6108 ( "@~" PGNSP PGUID b f f 114 6050 16 0 0 6049 contsel contjoinsel ));
+DESCR("jsonpath predicate");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 27e297e..aaeee04 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5579,6 +5579,19 @@ DESCR("implementation of @~ operator");
DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
DESCR("jsonpath predicate test");
+DATA(insert OID = 6043 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6044 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 114 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6045 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6046 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 114 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath query");
+DATA(insert OID = 6049 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6069 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index e39572e..9a8d77f 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
@@ -149,4 +192,22 @@ extern text *transform_json_string_values(text *json, void *action_state,
extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+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 0974ec8..5498b8a 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -224,10 +224,10 @@ typedef struct
} Jsonb;
/* convenience macros for accessing the root container in a Jsonb datum */
-#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK)
-#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0)
-#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0)
-#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0)
+#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root)
+#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root)
+#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root)
+#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root)
enum jbvType
@@ -276,6 +276,8 @@ struct JsonbValue
struct
{
int nPairs; /* 1 pair, 2 elements */
+ bool uniquified; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
JsonbPair *pairs;
} object; /* Associative container type */
@@ -370,6 +372,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,
@@ -378,6 +382,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 0000000..ee59344
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,110 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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))
+
+
+#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
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..2033906
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1661 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}';
+ ?column?
+----------
+ {"b": 1}
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b": 1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": 1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- unwrapping of operator arguments in lax mode
+select json '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select json '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select json '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select json '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select json '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select json '[]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonpath_predicate
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select json 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select json '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()';
+ ?column?
+----------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select json '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()';
+ERROR: SQL/JSON object not found
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T07:14:00+00:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:54:00+00:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:34:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-10T17:14:00+10:00"
+(1 row)
+
+select json '"10-03-2017 12:34 -05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")';
+ ?column?
+-----------------------------
+ "2017-03-11T03:54:00+10:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select json '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select json '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select json '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select json '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select json '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:34:56-08:00"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T01:24:56-08:00"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select json '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T00:00:00+00:00"
+(3 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T00:00:00+00:00"
+(5 rows)
+
+select json '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-09T21:02:03+00:00"
+(2 rows)
+
+-- time comparison
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select json '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]'
+ @* '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))';
+ ?column?
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select json '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]'
+ @* '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))';
+ ?column?
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:35:00+00:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T12:35:00+00:00"
+ "2017-03-10T13:35:00+00:00"
+ "2017-03-11"
+(5 rows)
+
+select json '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:35:00+00:00"
+ "2017-03-10T11:36:00+00:00"
+ "2017-03-10T14:35:00+00:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))';
+ ?column?
+-----------------------------
+ "2017-03-10T11:34:00+00:00"
+ "2017-03-10T10:35:00+00:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
+ ?column?
+----------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+ ?column?
+----------
+(0 rows)
+
+SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 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 20b2391..ccec68e 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 93bb283..f22a682 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -159,6 +159,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 0000000..a2b9518
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,361 @@
+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)';
+
+-- 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 > 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';
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-02-14 06:00 Oleg Bartunov <obartunov@gmail.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Oleg Bartunov @ 2018-02-14 06:00 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Wed, Feb 14, 2018 at 2:04 AM, Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> Attached 9th version of jsonpath patches rebased onto the latest master.
>
> Jsonpath grammar for parenthesized expressions and predicates was fixed.
>
> Documentation drafts for jsonpath written by Oleg Bartunov:
> https://github.com/obartunov/sqljsondoc
Direct link is https://github.com/obartunov/sqljsondoc/blob/master/README.jsonpath.md
Please consider it as WIP, I will update it in my spare time.
>
>
> --
> Nikita Glukhov
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-02-26 15:34 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Oleg Bartunov <obartunov@gmail.com>
0 siblings, 3 replies; 198+ messages in thread
From: Nikita Glukhov @ 2018-02-26 15:34 UTC (permalink / raw)
To: obartunov@gmail.com; +Cc: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Attached 10th version of the jsonpath patches.
1. Fixed error handling in arithmetic operators.
Now run-time errors in arithmetic operators are catched (added
PG_TRY/PG_CATCH around operator's functions calls) and converted into
Unknown values in predicates as it is required by the standard:
=# SELECT jsonb '[1,0,2]' @* '$[*] ? (1 / @ > 0)';
?column?
----------
1
2
(2 rows)
2. Fixed grammar for parenthesized expressions.
3. Refactored GIN support for jsonpath operators.
4. Added one more operator json[b] @# jsonpath returning singleton json[b] with
automatic conditional wrapping of sequences with more than one element into
arrays:
=# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 2)';
?column?
-----------
[3, 4, 5]
(1 row)
=# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 4)';
?column?
----------
5
(1 row)
=# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 5)';
?column?
----------
(null)
(1 row)
Existing set-returning operator json[b] @* jsonpath is also very userful but
can't be used in functional indices like new operator @#.
Note that conditional wrapping of @# differs from the wrapping in
JSON_QUERY(... WITH [ARRAY] WRAPPER), where only singleton objects and
arrays are not wrapped. Unconditional wrapping can be emulated with our
array construction feature (see below).
5. Changed time zone behavior in .datetime() item method.
In the previous version of the patch timestamptz SQL/JSON items were
serialized into JSON string items using session time zone. This behavior
did not allow jsonpath operators to be marked as immutable, and therefore
they could not be used in functional indices. Also, when the time zone was
not specified in the input string, but TZM or TZH format fields were present
in the format string, session time zone was used as a default for
timestamptz items.
To make jsonpath operators immutable we decided to save input time zone for
timestamptz items and disallow automatic time zone assignment. Also
additional parameter was added to .datetime() for default time zone
specification:
=# SET timezone = '+03';
SET
=# SELECT jsonb '"10-03-2017 12:34:56"' @*
'$.datetime("DD-MM-YYYY HH24:MI:SS TZH")';
ERROR: Invalid argument for SQL/JSON datetime function
=# SELECT jsonb '"10-03-2017 12:34:56"' @*
'$.datetime("DD-MM-YYYY HH24:MI:SS TZH", "+05")';
?column?
-----------------------------
"2017-03-10T12:34:56+05:00"
(1 row)
=# SELECT jsonb '"10-03-2017 12:34:56 +05"' @*
'$.datetime("DD-MM-YYYY HH24:MI:SS TZH")';
?column?
-----------------------------
"2017-03-10T12:34:56+05:00"
(1 row)
Please note that our .datetime() behavior is not standard now: by the
standard, input and format strings must match exactly, i.e. they both should
not contain trailing unmatched elements, so automatic time zone assignment
is impossible. But it too restrictive for PostgreSQL users, so we decided
to preserve usual PostgreSQL behavior here:
=# SELECT jsonb '"10-03-2017"' @* '$.datetime("DD-MM-YYYY HH24:MI:SS")';
?column?
-----------------------
"2017-03-10T00:00:00"
(1 row)
Also PostgreSQL is able to automatically recognize format of the input
string for the specified datetime type, but we can only bring this behavior
into jsonpath by introducing separate item methods .date(), .time(),
.timetz(), .timestamp() and .timestamptz(). Also we can use here our
unfinished feature that gives us ability to work with PostresSQL types in
jsonpath using cast operator :: (see sqljson_ext branch in our github repo):
=# SELECT jsonb '"10/03/2017 12:34"' @* '$::timestamptz';
?column?
-----------------------------
"2017-03-10T12:34:00+03:00"
(1 row)
A brief description of the extra jsonpath syntax features contained in the
patch #7:
* Sequence construction by joining path expressions with comma:
=# SELECT jsonb '[1, 2, 3]' @* '$[*], 4, 5';
?column?
----------
1
2
3
4
5
(5 rows)
* Array construction by placing sequence into brackets (equivalent to
JSON_QUERY(... WITH UNCONDITIONAL WRAPPER)):
=# SELECT jsonb '[1, 2, 3]' @* '[$[*], 4, 5]';
?column?
-----------------
[1, 2, 3, 4, 5]
(1 row)
* Object construction by placing sequences of key-value pairs into braces:
=# SELECT jsonb '{"a" : [1, 2, 3]}' @* '{a: [$.a[*], 4, 5], "b c": "dddd"}';
?column?
---------------------------------------
{"a": [1, 2, 3, 4, 5], "b c": "dddd"}
(1 row)
* Object subscripting with string-valued expressions:
=# SELECT jsonb '{"a" : "aaa", "b": "a", "c": "ccc"}' @* '$[$.b, "c"]';
?column?
----------
"aaa"
"ccc"
(2 rows)
* Support of UNIX epoch time in .datetime() item method:
=# SELECT jsonb '1519649957.37' @* '$.datetime()';
?column?
--------------------------------
"2018-02-26T12:59:17.37+00:00"
(1 row)
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (8 files, .tar.gz)
0001-strict-do_to_timestamp-v10.patch (text/x-patch, 3.8K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index b8bd4ca..25f25aa 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -967,7 +967,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);
@@ -984,7 +985,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);
@@ -2980,13 +2981,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;
@@ -3268,6 +3271,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/* select a DCHCacheEntry to hold the given format picture */
@@ -3569,7 +3589,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)
@@ -3604,7 +3624,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))
@@ -3637,9 +3657,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;
@@ -3693,7 +3715,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)
0002-pass-cstring-to-do_to_timestamp-v10.patch (text/x-patch, 2.7K) download
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 25f25aa..504bd26 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -985,8 +985,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);
@@ -3589,7 +3589,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)
@@ -3624,7 +3625,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))
@@ -3661,12 +3663,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;
@@ -3677,15 +3679,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)
{
/*
@@ -3717,11 +3719,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);
/*
0003-add-to_datetime-v10.patch (text/x-patch, 12.3K) download
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index eea2904..43d3470 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
#endif
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
/* common code for timetypmodin and timetztypmodin */
static int32
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1260,7 +1255,7 @@ time_in(PG_FUNCTION_ARGS)
/* tm2time()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
{
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1426,7 +1421,7 @@ time_scale(PG_FUNCTION_ARGS)
* have a fundamental tie together but rather a coincidence of
* implementation. - thomas
*/
-static void
+void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -2004,7 +1999,7 @@ time_part(PG_FUNCTION_ARGS)
/* tm2timetz()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
{
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 504bd26..37e97f2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -953,6 +954,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
@@ -986,7 +991,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);
@@ -3290,6 +3295,103 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
}
}
+/* Get mask of date/time/zone formatting components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3590,7 +3692,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)
@@ -3626,7 +3728,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))
@@ -3648,6 +3750,155 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
@@ -3659,12 +3910,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;
@@ -3719,6 +3974,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 103f91a..9b3344a 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 eb6d2a1..10cc822 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 d66582b..d3dd851 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 a9f5548..208cc00 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
0004-jsonpath-v10.patch (text/x-patch, 224.1K) download
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 9aa9b28..0011769 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -149,6 +149,12 @@
</row>
<row>
+ <entry><type>jsonpath</type></entry>
+ <entry></entry>
+ <entry>binary JSON path</entry>
+ </row>
+
+ <row>
<entry><type>line</type></entry>
<entry></entry>
<entry>infinite line on a plane</entry>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2f59af2..2c231e9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11133,6 +11133,7 @@ table2-mapping
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11142,6 +11143,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11150,6 +11152,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11157,6 +11160,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11164,6 +11168,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11171,6 +11176,7 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object at specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
@@ -11178,10 +11184,47 @@ table2-mapping
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object at specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@#</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for a specified JSON value (if there is more than one item, they will be wrapped into an array)</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @# '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>[3, 4, 5]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Does JSON path return any item for a specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for a specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index 731b469..f179bc4 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -569,4 +569,16 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
compared using the default database collation.
</para>
</sect2>
+
+ <sect2 id="json-path">
+ <title><type>jsonpath</type></title>
+ <indexterm>
+ <primary>json</primary>
+ <secondary>path</secondary>
+ </indexterm>
+
+ <para>
+ TODO
+ </para>
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267..23419cd 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -139,6 +139,9 @@ storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lw
utils/errcodes.h: utils/generate-errcodes.pl utils/errcodes.txt
$(MAKE) -C utils errcodes.h
+utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y
+ $(MAKE) -C utils/adt jsonpath_gram.h
+
# see explanation in parser/Makefile
utils/fmgrprotos.h: utils/fmgroids.h ;
@@ -169,7 +172,7 @@ submake-schemapg:
.PHONY: generated-headers
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/catalog/schemapg.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/errcodes.h $(top_builddir)/src/include/utils/fmgroids.h $(top_builddir)/src/include/utils/fmgrprotos.h $(top_builddir)/src/include/utils/probes.h $(top_builddir)/src/include/utils/jsonpath_gram.h
$(top_builddir)/src/include/parser/gram.h: parser/gram.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -186,6 +189,11 @@ $(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 $<)" .
+
$(top_builddir)/src/include/utils/errcodes.h: utils/errcodes.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
cd '$(dir $@)' && rm -f $(notdir $@) && \
@@ -220,6 +228,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h
$(MAKE) -C utils fmgrtab.c fmgroids.h fmgrprotos.h errcodes.h
+ $(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
@@ -308,6 +317,7 @@ endif
clean:
rm -f $(LOCALOBJS) postgres$(X) $(POSTGRES_IMP) \
$(top_builddir)/src/include/parser/gram.h \
+ $(top_builddir)/src/include/utils/jsonpath_gram.h \
$(top_builddir)/src/include/catalog/schemapg.h \
$(top_builddir)/src/include/storage/lwlocknames.h \
$(top_builddir)/src/include/utils/fmgroids.h \
@@ -344,6 +354,7 @@ maintainer-clean: distclean
utils/fmgrtab.c \
utils/errcodes.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 798a823..da0c098 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 4b35dbb..41b00fd 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -17,7 +17,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -32,6 +33,26 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution
+# tarball, so they are not cleaned here.
+clean distclean maintainer-clean:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 37e97f2..1890064 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3755,8 +3755,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;
@@ -3765,6 +3765,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict,
do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags);
*typmod = -1; /* TODO implement FF1, ..., FF9 */
+ *tz = 0;
if (flags & DCH_DATED)
{
@@ -3773,20 +3774,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")));
@@ -3850,20 +3858,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 6f0fe94..79eeac7 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 0f70180..0f20162 100644
--- a/src/backend/utils/adt/jsonb.c
+++ b/src/backend/utils/adt/jsonb.c
@@ -787,17 +787,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:
@@ -1845,3 +1845,27 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(out);
}
+
+/*
+ * Extract scalar value from raw-scalar pseudo-array jsonb.
+ */
+JsonbValue *
+JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
+{
+ JsonbIterator *it = JsonbIteratorInit(jbc);
+ JsonbIteratorToken tok PG_USED_FOR_ASSERTS_ONLY;
+ JsonbValue tmp;
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+ Assert(tmp.val.array.nElems == 1 && tmp.val.array.rawScalar);
+
+ tok = JsonbIteratorNext(&it, res, true);
+ Assert (tok == WJB_ELEM);
+ Assert(IsAJsonbScalar(res));
+
+ tok = JsonbIteratorNext(&it, &tmp, true);
+ Assert (tok == WJB_END_ARRAY);
+
+ return res;
+}
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 2524584..5332793 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,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..ffcdf25
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,869 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ bool allowCurrent, bool insideArraySubscript)
+{
+ /* position from begining of jsonpath data */
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ int32 chld, next;
+
+ check_stack_depth();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? 0 :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld;
+
+ chld = !item->value.args.right ? 0 :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ *(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,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld;
+ }
+ break;
+ case jpiFilter:
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ item->type == jpiFilter ||
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (!allowCurrent)
+ ereport(ERROR,
+ (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;
+ 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, true);
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ true, true);
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ *(int32*)(buf->data + next) =
+ flattenJsonPathParseItem(buf, item->next, allowCurrent,
+ insideArraySubscript);
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, false, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ 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);
+ else
+ 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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+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:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..233bb42
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2693 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+typedef struct JsonPathExecContext
+{
+ List *vars;
+ bool lax;
+ JsonbValue *root; /* for $ evaluation */
+ int innermostArraySize; /* for LAST array index evaluation */
+} JsonPathExecContext;
+
+typedef struct JsonValueListIterator
+{
+ ListCell *lcell;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
+
+
+static inline void
+JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+{
+ if (jvl->singleton)
+ {
+ jvl->list = list_make2(jvl->singleton, jbv);
+ jvl->singleton = NULL;
+ }
+ else if (!jvl->list)
+ jvl->singleton = jbv;
+ else
+ jvl->list = lappend(jvl->list, jbv);
+}
+
+static inline void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (jvl1->singleton)
+ {
+ if (jvl2.singleton)
+ jvl1->list = list_make2(jvl1->singleton, jvl2.singleton);
+ else
+ jvl1->list = lcons(jvl1->singleton, jvl2.list);
+
+ jvl1->singleton = NULL;
+ }
+ else if (jvl2.singleton)
+ {
+ if (jvl1->list)
+ jvl1->list = lappend(jvl1->list, jvl2.singleton);
+ else
+ jvl1->singleton = jvl2.singleton;
+ }
+ else if (jvl1->list)
+ jvl1->list = list_concat(jvl1->list, jvl2.list);
+ else
+ jvl1->list = jvl2.list;
+}
+
+static inline int
+JsonValueListLength(const JsonValueList *jvl)
+{
+ return jvl->singleton ? 1 : list_length(jvl->list);
+}
+
+static inline bool
+JsonValueListIsEmpty(JsonValueList *jvl)
+{
+ return !jvl->singleton && list_length(jvl->list) <= 0;
+}
+
+static inline JsonbValue *
+JsonValueListHead(JsonValueList *jvl)
+{
+ return jvl->singleton ? jvl->singleton : linitial(jvl->list);
+}
+
+static inline void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static inline List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ if (jvl->singleton)
+ return list_make1(jvl->singleton);
+
+ return jvl->list;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static void
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ foreach(cell, vars)
+ {
+ var = (JsonPathVariable*)lfirst(cell);
+
+ if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) &&
+ !strncmp(varName, VARDATA_ANY(var->varName), varNameLength))
+ break;
+
+ var = NULL;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return;
+ }
+
+ switch(var->typid)
+ {
+ case BOOLOID:
+ value->type = jbvBool;
+ value->val.boolean = DatumGetBool(computedValue);
+ break;
+ case NUMERICOID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(computedValue);
+ break;
+ break;
+ case INT2OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int2_numeric, computedValue));
+ break;
+ case INT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int4_numeric, computedValue));
+ break;
+ case INT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric, computedValue));
+ break;
+ case FLOAT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case FLOAT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ value->type = jbvString;
+ value->val.string.val = VARDATA_ANY(computedValue);
+ value->val.string.len = VARSIZE_ANY_EXHDR(computedValue);
+ break;
+ case 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static void
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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, cxt->vars, value);
+ break;
+ default:
+ elog(ERROR, "Wrong type");
+ }
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathExecResult
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jperOk : jperNotFound;
+
+ return jperError;
+ }
+
+ 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;
+ 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;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jperError;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jperOk : jperNotFound;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathExecResult
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jperError;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jperNotFound;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jperError;
+ }
+
+ return res ? jperOk : jperNotFound;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (cxt->lax)
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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 satifistfying
+ * condition. In strict mode all pairs should be comparable, otherwise an error
+ * is returned.
+ */
+static JsonPathExecResult
+executeExpr(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperError;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ 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 (cxt->lax)
+ return jperOk;
+
+ found = true;
+ }
+ else if (res == jperError)
+ {
+ if (!cxt->lax)
+ return jperError;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jperOk;
+
+ if (error) /* possible only in lax mode */
+ return jperError;
+
+ return jperNotFound;
+}
+
+/*
+ * 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)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ 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;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ return recursiveExecuteNext(cxt, jsp, &elem, lval, found, false);
+}
+
+/*
+ * Execute unary arithemitc 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursivly iterate over jsonb objects/arrays
+ */
+ 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 */
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathExecResult
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ 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 (JsonValueListLength(&rseq) != 1)
+ return jperError;
+
+ initial = JsonValueListHead(&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;
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ 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;
+}
+
+static JsonPathExecResult
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperError;
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ 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;
+}
+
+/*
+ * 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 *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, fmt, fmtlen, tzname, strict,
+ typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ return ok;
+}
+
+/*
+ * Convert execution status 'res' to a boolean JSON item and execute next
+ * jsonpath if 'needBool' is false:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => null (errors are converted to NULL values)
+ */
+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; /* simply return status */
+ }
+
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found, bool needBool)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+
+ check_stack_depth();
+
+ switch(jsp->type) {
+ 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 jperOk:
+ res = jperNotFound;
+ break;
+ case jperNotFound:
+ res = jperOk;
+ break;
+ default:
+ 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)
+ {
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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)
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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;
+ }
+
+ JsonbExtractScalar(jb->val.binary.data, v);
+ }
+ else
+ v = jb;
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+ break;
+ }
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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);
+ break;
+
+ case jpiIndexArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonbArraySize(jb);
+ bool binary = jb->type == jbvBinary;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!cxt->lax &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!cxt->lax)
+ {
+ Assert(found);
+ 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:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ 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;
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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);
+
+ if (cxt->lax)
+ res = recursiveExecute(cxt, &elem, jb, NULL);
+ else
+ {
+ 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;
+ }
+
+ res = appendBoolResult(cxt, jsp, found, res, needBool);
+ break;
+ case jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ {
+ 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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist, false);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ if (tryToParseDatetime(template_str, template_len, datetime,
+ tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ {
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ 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);
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ 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);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ for (; elem < last; elem++)
+ {
+ res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
+
+ 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, false);
+ if (jperIsError(res))
+ break;
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+
+ 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)
+{
+ if (cxt->lax && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+static inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (cxt->lax)
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* 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:
+ case jpiIndexArray:
+ jb = wrapItem(jb);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
+}
+
+/*
+ * Execute boolean-valued jsonpath expression. Boolean items are not appended
+ * to the result list, only return code determines result:
+ * - jperOk => true
+ * - jperNotFound => false
+ * - jperError => NULL (errors are converted to NULL values)
+ */
+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
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONBOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static 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)
+{
+ 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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ PG_RETURN_NULL();
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..7300f76
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,489 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+#include "utils/jsonpath_scanner.h"
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+static JsonPathParseItem*
+makeItemType(int type)
+{
+ JsonPathParseItem* v = palloc(sizeof(*v));
+
+ v->type = type;
+ v->next = NULL;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemString(string *s)
+{
+ JsonPathParseItem *v;
+
+ if (s == NULL)
+ {
+ v = makeItemType(jpiNull);
+ }
+ else
+ {
+ v = makeItemType(jpiString);
+ v->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first > 0) ? first : 0;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); };
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); };
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+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)); }
+ ;
+
+accessor_op:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..aad4aa2
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,557 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 xVARQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\" \t\n\r\f]
+blank [ \t\n\r\f]
+unicode \\u[0-9A-Fa-f]{4}
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\") {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\[\"\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 5025a44..dfe8993 100644
--- a/src/backend/utils/adt/regexp.c
+++ b/src/backend/utils/adt/regexp.c
@@ -129,7 +129,7 @@ static Datum build_regexp_split_result(regexp_matches_ctx *splitctx);
* Pattern is given in the database encoding. We internally convert to
* an array of pg_wchar, which is what Spencer's regex package wants.
*/
-static regex_t *
+regex_t *
RE_compile_and_cache(text *text_re, int cflags, Oid collation)
{
int text_re_len = VARSIZE_ANY_EXHDR(text_re);
@@ -335,7 +335,7 @@ RE_execute(regex_t *re, char *dat, int dat_len,
* Both pattern and data are given in the database encoding. We internally
* convert to array of pg_wchar which is what Spencer's regex package wants.
*/
-static bool
+bool
RE_compile_and_execute(text *text_re, char *dat, int dat_len,
int cflags, Oid collation,
int nmatch, regmatch_t *pmatch)
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 9871d1e..27948ec 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
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index e74f963..af4dedc 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1853,5 +1853,13 @@ DATA(insert OID = 3286 ( "-" PGNSP PGUID b f f 3802 23 3802 0 0 3303 - - ));
DESCR("delete array element");
DATA(insert OID = 3287 ( "#-" PGNSP PGUID b f f 3802 1009 3802 0 0 jsonb_delete_path - - ));
DESCR("delete path");
+DATA(insert OID = 6075 ( "@*" PGNSP PGUID b f f 3802 6050 3802 0 0 6055 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6076 ( "@?" PGNSP PGUID b f f 3802 6050 16 0 0 6054 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contsel contjoinsel ));
+DESCR("jsonpath predicate");
+DATA(insert OID = 6122 ( "@#" PGNSP PGUID b f f 3802 6050 3802 0 0 6124 - - ));
+DESCR("jsonpath items wrapped");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index c00d055..8056652 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5575,6 +5575,28 @@ DESCR("list of files in the WAL directory");
DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 4 0 16 "26 23 23 2276" _null_ "{i,i,i,v}" _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ ));
DESCR("hash partition CHECK constraint");
+/* jsonpath */
+DATA(insert OID = 6052 ( jsonpath_in PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 6050 "2275" _null_ _null_ _null_ _null_ _null_ jsonpath_in _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6053 ( jsonpath_out PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2275 "6050" _null_ _null_ _null_ _null_ _null_ jsonpath_out _null_ _null_ _null_ ));
+DESCR("I/O");
+DATA(insert OID = 6054 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6055 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 3802 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6124 ( jsonpath_query_wrapped PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3802 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query_wrapped2 _null_ _null_ _null_ ));
+DESCR("implementation of @# operator");
+DATA(insert OID = 6056 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6057 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 3802 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath object test");
+DATA(insert OID = 6125 ( jsonpath_query_wrapped PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 3802 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_query_wrapped3 _null_ _null_ _null_ ));
+DESCR("jsonpath query with conditional wrapper");
+DATA(insert OID = 6073 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3802 6050" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 5b5b121..5103ef9 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -638,6 +638,12 @@ DESCR("Binary JSON");
#define JSONBOID 3802
DATA(insert OID = 3807 ( _jsonb PGNSP PGUID -1 f b A f t \054 0 3802 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+/* jsonpath */
+DATA(insert OID = 6050 ( jsonpath PGNSP PGUID -1 f b U f t \054 0 0 6051 jsonpath_in jsonpath_out - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ ));
+DESCR("JSON Path");
+#define JSONPATHOID 6050
+DATA(insert OID = 6051 ( _jsonpath PGNSP PGUID -1 f b A f t \054 0 6050 0 array_in array_out array_recv array_send - - array_typanalyze i x f 0 -1 0 0 _null_ _null_ _null_ ));
+
DATA(insert OID = 2970 ( txid_snapshot PGNSP PGUID -1 f b U f t \054 0 0 2949 txid_snapshot_in txid_snapshot_out txid_snapshot_recv txid_snapshot_send - - - d x f 0 -1 0 0 _null_ _null_ _null_ ));
DESCR("txid snapshot");
DATA(insert OID = 2949 ( _txid_snapshot PGNSP PGUID -1 f b A f t \054 0 2970 0 array_in array_out array_recv array_send - - array_typanalyze d x f 0 -1 0 0 _null_ _null_ _null_ ));
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/formatting.h b/src/include/utils/formatting.h
index 208cc00..6db5b3f 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 e39572e..4629c45 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -147,6 +147,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 27873d4..b306b26 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;
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +281,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +392,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 0000000..d06bb14
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,270 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+extern JsonPathParseResult* parsejsonpath(const char *str, int len);
+
+/*
+ * Evaluation of jsonpath
+ */
+
+typedef enum JsonPathExecStatus
+{
+ 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)
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **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 0000000..033e5af
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1697 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?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,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(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"}}
+(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"}}
+(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"}}
+(3 rows)
+
+select jsonb 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..0b2f1bb
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,770 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.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 '$+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 ? ($.a == 1)'::jsonpath;
+ jsonpath
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 ad9434f..20b2391 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 27cd498..93bb283 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -158,6 +158,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 0000000..8b36a30
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,383 @@
+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)');
+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}';
+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
+ 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..32c33f1
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,140 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 '$+1'::jsonpath;
+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;
+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 ? (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;
+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.size() - 3]'::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;
+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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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;
0005-jsonpath-gin-v10.patch (text/x-patch, 42.9K) download
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index f179bc4..1be32b7 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -536,6 +536,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index c8a2745..ecdcb2e 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,440 @@ 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 = palloc(sizeof(**extra_data) * entries.count);
+ **extra_data = (Pointer) node;
+
+ return entries.buf;
+}
+
+static GinTernaryValue
+gin_execute_jsonpath(JsonPathNode *node, GinTernaryValue *check)
+{
+ 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);
+ 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);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eEntry:
+ return check[node->val.entryIndex] ? GIN_MAYBE : GIN_FALSE;
+
+ default:
+ elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+ return GIN_FALSE;
+ }
}
Datum
@@ -181,6 +737,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 +767,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 +824,13 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check) != GIN_FALSE;
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +845,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 +882,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);
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -331,14 +911,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 +927,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 +940,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 +970,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 +991,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 +1004,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 +1045,40 @@ 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)
+ {
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1091,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);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_GIN_TERNARY_VALUE(res);
}
diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h
index 03af581..e05ca03 100644
--- a/src/include/catalog/pg_amop.h
+++ b/src/include/catalog/pg_amop.h
@@ -821,11 +821,15 @@ DATA(insert ( 4036 3802 3802 7 s 3246 2742 0 ));
DATA(insert ( 4036 3802 25 9 s 3247 2742 0 ));
DATA(insert ( 4036 3802 1009 10 s 3248 2742 0 ));
DATA(insert ( 4036 3802 1009 11 s 3249 2742 0 ));
+DATA(insert ( 4036 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4036 3802 6050 16 s 6107 2742 0 ));
/*
* GIN jsonb_path_ops
*/
DATA(insert ( 4037 3802 3802 7 s 3246 2742 0 ));
+DATA(insert ( 4037 3802 6050 15 s 6076 2742 0 ));
+DATA(insert ( 4037 3802 6050 16 s 6107 2742 0 ));
/*
* SP-GiST range_ops
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index b306b26..144b8b0 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 d06bb14..87dae0d 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 465195a..178e06f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2708,6 +2708,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
@@ -2783,6 +2891,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42
(1 row)
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ QUERY PLAN
+-----------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @~ '($."wait" == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @~ '($."wait" == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+ count
+-------
+ 337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+ count
+-------
+ 42
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -2933,6 +3231,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
1012
(1 row)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+ count
+-------
+ 1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ QUERY PLAN
+-------------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on testjsonb
+ Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+ -> Bitmap Index Scan on jidx
+ Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count
+-------
+ 1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count
+-------
+ 15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count
+-------
+ 2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count
+-------
+ 3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count
+-------
+ 1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count
+-------
+ 194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count
+-------
+ 0
+(1 row)
+
RESET enable_seqscan;
DROP INDEX jidx;
-- nested tests
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 6616cc1..67479c4 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1823,6 +1823,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1886,7 +1888,7 @@ ORDER BY 1, 2, 3;
4000 | 25 | <<=
4000 | 26 | >>
4000 | 27 | >>=
-(121 rows)
+(123 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 903e5ef..b153e62 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -733,6 +733,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off;
@@ -751,6 +769,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar';
SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.public) && exists($.disabled)';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
-- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
@@ -800,6 +851,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
-- exercise GIN_SEARCH_MODE_ALL
SELECT count(*) FROM testjsonb WHERE j @> '{}';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @~ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @~ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @~ 'exists($)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
RESET enable_seqscan;
DROP INDEX jidx;
0006-jsonpath-json-v10.patch (text/x-patch, 96.5K) download
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b00fd..e2ad685 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -17,7 +17,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
@@ -47,6 +47,8 @@ jsonpath_gram.h: jsonpath_gram.c ;
# Force these dependencies to be known even without dependency info built:
jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+jsonpath_json.o: jsonpath_exec.c
+
# jsonpath_gram.c, jsonpath_gram.h, and jsonpath_scan.c are in the distribution
# tarball, so they are not cleaned here.
clean distclean maintainer-clean:
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 79eeac7..b19d7b1 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,804 @@ 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,
+ &jbv->val.datetime.tz);
+
+ 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 5332793..5151e8d 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 @@ recurse:
/* 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 233bb42..ee5fcf2 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -23,6 +23,12 @@
#include "utils/jsonpath.h"
#include "utils/varlena.h"
+#ifdef JSONPATH_JSON_C
+#define JSONXOID JSONOID
+#else
+#define JSONXOID JSONBOID
+#endif
+
typedef struct JsonPathExecContext
{
List *vars;
@@ -155,6 +161,7 @@ JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
return lfirst(it->lcell);
}
+#ifndef JSONPATH_JSON_C
/*
* Initialize a binary JsonbValue with the given jsonb container.
*/
@@ -167,6 +174,7 @@ JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
return jbv;
}
+#endif
/*
* Transform a JsonbValue into a binary JsonbValue by encoding it to a
@@ -279,7 +287,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);
@@ -345,7 +353,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;
@@ -370,7 +378,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);
@@ -431,7 +439,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);
@@ -2410,7 +2418,7 @@ makePassingVars(Jsonb *jb)
jpv->cb_arg = v.val.numeric;
break;
case jbvBinary:
- jpv->typid = JSONBOID;
+ jpv->typid = JSONXOID;
jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
break;
default:
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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.h b/src/include/catalog/pg_operator.h
index af4dedc..ecdf2c8 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -1861,5 +1861,13 @@ DATA(insert OID = 6107 ( "@~" PGNSP PGUID b f f 3802 6050 16 0 0 6073 contse
DESCR("jsonpath predicate");
DATA(insert OID = 6122 ( "@#" PGNSP PGUID b f f 3802 6050 3802 0 0 6124 - - ));
DESCR("jsonpath items wrapped");
+DATA(insert OID = 6070 ( "@*" PGNSP PGUID b f f 114 6050 114 0 0 6044 - - ));
+DESCR("jsonpath items");
+DATA(insert OID = 6071 ( "@?" PGNSP PGUID b f f 114 6050 16 0 0 6043 contsel contjoinsel ));
+DESCR("jsonpath exists");
+DATA(insert OID = 6108 ( "@~" PGNSP PGUID b f f 114 6050 16 0 0 6049 contsel contjoinsel ));
+DESCR("jsonpath predicate");
+DATA(insert OID = 6123 ( "@#" PGNSP PGUID b f f 114 6050 114 0 0 6126 - - ));
+DESCR("jsonpath items wrapped");
#endif /* PG_OPERATOR_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 8056652..86a39fb 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5597,6 +5597,23 @@ DESCR("implementation of @~ operator");
DATA(insert OID = 6074 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "3802 6050 3802" _null_ _null_ _null_ _null_ _null_ jsonb_jsonpath_predicate3 _null_ _null_ _null_ ));
DESCR("jsonpath predicate test");
+DATA(insert OID = 6043 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists2 _null_ _null_ _null_ ));
+DESCR("implementation of @? operator");
+DATA(insert OID = 6044 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 2 0 114 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query2 _null_ _null_ _null_ ));
+DESCR("implementation of @* operator");
+DATA(insert OID = 6126 ( jsonpath_query_wrapped PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 114 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query_wrapped2 _null_ _null_ _null_ ));
+DESCR("implementation of @# operator");
+DATA(insert OID = 6045 ( jsonpath_exists PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_exists3 _null_ _null_ _null_ ));
+DESCR("jsonpath exists test");
+DATA(insert OID = 6046 ( jsonpath_query PGNSP PGUID 12 1 1000 0 0 f f f f t t i s 3 0 114 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query3 _null_ _null_ _null_ ));
+DESCR("jsonpath query");
+DATA(insert OID = 6127 ( jsonpath_query_wrapped PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 114 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_query_wrapped3 _null_ _null_ _null_ ));
+DESCR("implementation of @# operator");
+DATA(insert OID = 6049 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "114 6050" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate2 _null_ _null_ _null_ ));
+DESCR("implementation of @~ operator");
+DATA(insert OID = 6069 ( jsonpath_predicate PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "114 6050 114" _null_ _null_ _null_ _null_ _null_ json_jsonpath_predicate3 _null_ _null_ _null_ ));
+DESCR("jsonpath predicate test");
+
/*
* Symbolic values for provolatile column: these indicate whether the result
* of a function is dependent *only* on the values of its explicit arguments,
diff --git a/src/include/utils/jsonapi.h b/src/include/utils/jsonapi.h
index 4629c45..67c3031 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
@@ -149,4 +192,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 144b8b0..2ea1ec1 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -224,10 +224,10 @@ typedef struct
} Jsonb;
/* convenience macros for accessing the root container in a Jsonb datum */
-#define JB_ROOT_COUNT(jbp_) (*(uint32 *) VARDATA(jbp_) & JB_CMASK)
-#define JB_ROOT_IS_SCALAR(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FSCALAR) != 0)
-#define JB_ROOT_IS_OBJECT(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FOBJECT) != 0)
-#define JB_ROOT_IS_ARRAY(jbp_) ((*(uint32 *) VARDATA(jbp_) & JB_FARRAY) != 0)
+#define JB_ROOT_COUNT(jbp_) JsonContainerSize(&(jbp_)->root)
+#define JB_ROOT_IS_SCALAR(jbp_) JsonContainerIsScalar(&(jbp_)->root)
+#define JB_ROOT_IS_OBJECT(jbp_) JsonContainerIsObject(&(jbp_)->root)
+#define JB_ROOT_IS_ARRAY(jbp_) JsonContainerIsArray(&(jbp_)->root)
enum jbvType
@@ -276,6 +276,8 @@ struct JsonbValue
struct
{
int nPairs; /* 1 pair, 2 elements */
+ bool uniquified; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
JsonbPair *pairs;
} object; /* Associative container type */
@@ -371,6 +373,8 @@ typedef struct JsonbIterator
/* Support functions */
extern uint32 getJsonbOffset(const JsonbContainer *jc, int index);
extern uint32 getJsonbLength(const JsonbContainer *jc, int index);
+extern int lengthCompareJsonbStringValue(const void *a, const void *b);
+extern bool equalsJsonbScalarValue(JsonbValue *a, JsonbValue *b);
extern int compareJsonbContainers(JsonbContainer *a, JsonbContainer *b);
extern JsonbValue *findJsonbValueFromContainer(JsonbContainer *sheader,
uint32 flags,
@@ -379,6 +383,8 @@ extern JsonbValue *getIthJsonbValueFromContainer(JsonbContainer *sheader,
uint32 i);
extern JsonbValue *pushJsonbValue(JsonbParseState **pstate,
JsonbIteratorToken seq, JsonbValue *jbVal);
+extern JsonbValue *pushJsonbValueScalar(JsonbParseState **pstate,
+ JsonbIteratorToken seq,JsonbValue *scalarVal);
extern JsonbIterator *JsonbIteratorInit(JsonbContainer *container);
extern JsonbIteratorToken JsonbIteratorNext(JsonbIterator **it, JsonbValue *val,
bool skipNested);
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..a751540
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_json.h
+ * Jsonpath support for json datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/utils/jsonpath_json.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSONPATH_JSON_H
+#define JSONPATH_JSON_H
+
+/* redefine jsonb structures */
+#define Jsonb Json
+#define JsonbContainer JsonContainer
+#define JsonbIterator JsonIterator
+
+/* redefine jsonb functions */
+#define findJsonbValueFromContainer(jc, flags, jbv) \
+ findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv)
+#define getIthJsonbValueFromContainer(jc, i) \
+ getIthJsonValueFromContainer((JsonContainer *)(jc), i)
+#define pushJsonbValue pushJsonValue
+#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc))
+#define JsonbIteratorNext JsonIteratorNext
+#define JsonbValueToJsonb JsonbValueToJson
+#define JsonbToCString JsonToCString
+#define JsonbUnquote JsonUnquote
+#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv)
+
+/* redefine jsonb macros */
+#undef JsonContainerSize
+#define JsonContainerSize(jc) \
+ ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \
+ JsonContainerIsArray(jc) \
+ ? JsonGetArraySize((JsonContainer *)(jc)) \
+ : ((JsonContainer *)(jc))->header & JB_CMASK)
+
+
+#undef DatumGetJsonbP
+#define DatumGetJsonbP(d) DatumGetJsonP(d)
+
+#undef DatumGetJsonbPCopy
+#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d)
+
+#undef JsonbPGetDatum
+#define JsonbPGetDatum(json) JsonPGetDatum(json)
+
+#undef PG_GETARG_JSONB_P
+#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n))
+
+#undef PG_GETARG_JSONB_P_COPY
+#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n))
+
+#undef PG_RETURN_JSONB_P
+#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json))
+
+
+#ifdef DatumGetJsonb
+#undef DatumGetJsonb
+#define DatumGetJsonb(d) DatumGetJsonbP(d)
+#endif
+
+#ifdef DatumGetJsonbCopy
+#undef DatumGetJsonbCopy
+#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d)
+#endif
+
+#ifdef JsonbGetDatum
+#undef JsonbGetDatum
+#define JsonbGetDatum(json) JsonbPGetDatum(json)
+#endif
+
+#ifdef PG_GETARG_JSONB
+#undef PG_GETARG_JSONB
+#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n)
+#endif
+
+#ifdef PG_GETARG_JSONB_COPY
+#undef PG_GETARG_JSONB_COPY
+#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n)
+#endif
+
+#ifdef PG_RETURN_JSONB
+#undef PG_RETURN_JSONB
+#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json)
+#endif
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..cec88a0
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1718 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}';
+ ?column?
+----------
+ {"b": 1}
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2,}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3,}';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1,2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2,3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1,2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2,3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b": 1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": 1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select json '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)';
+ ?column?
+----------
+ 0
+(1 row)
+
+select json '0' @* '1 / $';
+ERROR: Unknown SQL/JSON error
+-- unwrapping of operator arguments in lax mode
+select json '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select json '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select json '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select json '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select json '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select json '[]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonpath_predicate
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select json 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select json '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()';
+ ?column?
+----------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select json '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()';
+ERROR: SQL/JSON object not found
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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 20b2391..ccec68e 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 93bb283..f22a682 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -159,6 +159,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 0000000..7504e14
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,377 @@
+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"' @* '$.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")';
+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"' @* '$.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")';
+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';
0007-jsonpath-extras-v10.patch (text/x-patch, 37.2K) download
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index ffcdf25..89fa38c 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -132,12 +132,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
item->type == jpiFilter ||
allowCurrent,
@@ -215,6 +218,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 pos =
+ flattenJsonPathParseItem(buf, lfirst(lc),
+ allowCurrent, insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos;
+ ppos[1] = valpos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -300,6 +358,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -489,12 +549,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -553,6 +613,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -575,7 +683,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -686,6 +794,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -697,6 +806,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -711,7 +830,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -763,7 +883,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -867,3 +990,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 7f68d31..d9571d2 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -109,6 +109,8 @@ static inline JsonPathExecResult recursiveExecuteBool(JsonPathExecContext *cxt,
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1697,8 +1699,126 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
+ else if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+ JsonbValue *wrapped = NULL;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!cxt->lax)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (!wrapped)
+ wrapped = wrapItem(jb);
+
+ res = getArrayIndex(cxt, &from, wrapped, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, wrapped, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && cxt->lax)
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!cxt->lax)
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
else
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ {
+ if (cxt->lax)
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found, false);
+ else
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ }
break;
case jpiLast:
@@ -1993,7 +2113,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2004,84 +2123,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
- if (jsp->content.args.left)
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist, false);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist, false);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- if (tryToParseDatetime(template_str, template_len, datetime,
- tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tzname)
- pfree(tzname);
- }
- else
- {
- const char *templates[] = {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- int i;
-
- for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ if (tryToParseDatetime(template_str, template_len,
+ datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (tryToParseDatetime(templates[i], -1, datetime,
- NULL, true, &value, &typid,
- &typmod, &tz))
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
{
- res = jperOk;
- break;
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2192,6 +2340,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = executeLikeRegexPredicate(cxt, jsp, jb);
res = appendBoolResult(cxt, jsp, found, res, needBool);
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
@@ -2327,7 +2602,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
return recursiveExecuteUnwrap(cxt, jsp, jb, found);
case jpiAnyArray:
- case jpiIndexArray:
jb = wrapItem(jb);
break;
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 7300f76..7be36bc 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -252,6 +252,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -284,9 +304,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -309,7 +329,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -322,6 +342,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -376,6 +410,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index c3c5316..c57c3f6 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -86,6 +86,10 @@ typedef enum JsonPathItemType {
jpiLast,
jpiStartsWith,
jpiLikeRegex,
+ jpiSequence,
+ jpiArray,
+ jpiObject,
+ jpiObjectField,
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -140,6 +144,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -166,6 +183,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -211,6 +231,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index cec88a0..dc0be2a 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 033e5af..a80d2a6 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1165,8 +1203,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1178,6 +1214,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1653,6 +1708,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1671,6 +1732,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1695,3 +1762,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 0b2f1bb..38417e3 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -480,6 +480,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 8b36a30..c4043c9 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -238,12 +248,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -371,13 +385,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 32c33f1..b36113c 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -90,6 +90,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
0008-jsonpath-extras-tests-for-json-v10.patch (text/x-patch, 8.2K) download
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index dc0be2a..6b8198f 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -1214,6 +1214,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1674,6 +1693,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1692,6 +1717,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1716,3 +1747,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 7504e14..c10336a 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -253,6 +253,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -365,13 +370,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-02-26 19:36 Oleg Bartunov <obartunov@gmail.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
2 siblings, 0 replies; 198+ messages in thread
From: Oleg Bartunov @ 2018-02-26 19:36 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Mon, Feb 26, 2018 at 6:34 PM, Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> Attached 10th version of the jsonpath patches.
>
> 1. Fixed error handling in arithmetic operators.
>
> Now run-time errors in arithmetic operators are catched (added
> PG_TRY/PG_CATCH around operator's functions calls) and converted into
> Unknown values in predicates as it is required by the standard:
>
> =# SELECT jsonb '[1,0,2]' @* '$[*] ? (1 / @ > 0)';
> ?column?
> ----------
> 1
> 2
> (2 rows)
>
> 2. Fixed grammar for parenthesized expressions.
>
> 3. Refactored GIN support for jsonpath operators.
>
> 4. Added one more operator json[b] @# jsonpath returning singleton json[b]
> with
> automatic conditional wrapping of sequences with more than one element
> into
> arrays:
>
> =# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 2)';
> ?column?
> -----------
> [3, 4, 5]
> (1 row)
>
> =# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 4)';
> ?column?
> ----------
> 5
> (1 row)
>
> =# SELECT jsonb '[1,2,3,4,5]' @# '$[*] ? (@ > 5)';
> ?column?
> ----------
> (null)
> (1 row)
>
> Existing set-returning operator json[b] @* jsonpath is also very userful
> but
> can't be used in functional indices like new operator @#.
>
> Note that conditional wrapping of @# differs from the wrapping in
> JSON_QUERY(... WITH [ARRAY] WRAPPER), where only singleton objects and
> arrays are not wrapped. Unconditional wrapping can be emulated with our
> array construction feature (see below).
>
> 5. Changed time zone behavior in .datetime() item method.
>
> In the previous version of the patch timestamptz SQL/JSON items were
> serialized into JSON string items using session time zone. This behavior
> did not allow jsonpath operators to be marked as immutable, and therefore
> they could not be used in functional indices. Also, when the time zone
> was
> not specified in the input string, but TZM or TZH format fields were
> present
> in the format string, session time zone was used as a default for
> timestamptz items.
>
> To make jsonpath operators immutable we decided to save input time zone
> for
> timestamptz items and disallow automatic time zone assignment. Also
> additional parameter was added to .datetime() for default time zone
> specification:
>
> =# SET timezone = '+03';
> SET
>
> =# SELECT jsonb '"10-03-2017 12:34:56"' @*
> '$.datetime("DD-MM-YYYY HH24:MI:SS TZH")';
> ERROR: Invalid argument for SQL/JSON datetime function
>
> =# SELECT jsonb '"10-03-2017 12:34:56"' @*
> '$.datetime("DD-MM-YYYY HH24:MI:SS TZH", "+05")';
> ?column?
> -----------------------------
> "2017-03-10T12:34:56+05:00"
> (1 row)
>
> =# SELECT jsonb '"10-03-2017 12:34:56 +05"' @*
> '$.datetime("DD-MM-YYYY HH24:MI:SS TZH")';
> ?column?
> -----------------------------
> "2017-03-10T12:34:56+05:00"
> (1 row)
>
> Please note that our .datetime() behavior is not standard now: by the
> standard, input and format strings must match exactly, i.e. they both
> should
> not contain trailing unmatched elements, so automatic time zone
> assignment
> is impossible. But it too restrictive for PostgreSQL users, so we
> decided
> to preserve usual PostgreSQL behavior here:
>
> =# SELECT jsonb '"10-03-2017"' @* '$.datetime("DD-MM-YYYY HH24:MI:SS")';
> ?column?
> -----------------------
> "2017-03-10T00:00:00"
> (1 row)
I think someday we should consider adding support for sql standard
conforming datetime. Since it
breaks postgres behaviour we will need 'standard_conforming_datetime' guc.
>
>
> Also PostgreSQL is able to automatically recognize format of the input
> string for the specified datetime type, but we can only bring this
> behavior
> into jsonpath by introducing separate item methods .date(), .time(),
> .timetz(), .timestamp() and .timestamptz(). Also we can use here our
> unfinished feature that gives us ability to work with PostresSQL types in
> jsonpath using cast operator :: (see sqljson_ext branch in our github
> repo):
>
> =# SELECT jsonb '"10/03/2017 12:34"' @* '$::timestamptz';
> ?column?
> -----------------------------
> "2017-03-10T12:34:00+03:00"
> (1 row)
Another note.
We decided to preserve TZ in JSON_QUERY function and follow standard
Postgres behaviour in JSON_VALUE,
since JSON_QUERY returns JSON object and JSON_VALUE returns SQL value.
SELECT JSON_QUERY(jsonb '"2018-02-21 17:01:23 +05"',
'$.datetime("YYYY-MM-DD HH24:MI:SS TZH")');
json_query
-----------------------------
"2018-02-21T17:01:23+05:00"
(1 row)
show timezone;
TimeZone
----------
W-SU
(1 row)
SELECT JSON_VALUE(jsonb '"2018-02-21 17:01:23 +05"',
'$.datetime("YYYY-MM-DD HH24:MI:SS TZH")');
json_value
------------------------
2018-02-21 15:01:23+03
(1 row)
>
>
>
> A brief description of the extra jsonpath syntax features contained in the
> patch #7:
>
> * Sequence construction by joining path expressions with comma:
>
> =# SELECT jsonb '[1, 2, 3]' @* '$[*], 4, 5';
> ?column?
> ----------
> 1
> 2
> 3
> 4
> 5
> (5 rows)
>
> * Array construction by placing sequence into brackets (equivalent to
> JSON_QUERY(... WITH UNCONDITIONAL WRAPPER)):
>
> =# SELECT jsonb '[1, 2, 3]' @* '[$[*], 4, 5]';
> ?column?
> -----------------
> [1, 2, 3, 4, 5]
> (1 row)
>
> * Object construction by placing sequences of key-value pairs into braces:
>
> =# SELECT jsonb '{"a" : [1, 2, 3]}' @* '{a: [$.a[*], 4, 5], "b c":
> "dddd"}';
> ?column?
> ---------------------------------------
> {"a": [1, 2, 3, 4, 5], "b c": "dddd"}
> (1 row)
>
> * Object subscripting with string-valued expressions:
>
> =# SELECT jsonb '{"a" : "aaa", "b": "a", "c": "ccc"}' @* '$[$.b, "c"]';
> ?column?
> ----------
> "aaa"
> "ccc"
> (2 rows)
>
> * Support of UNIX epoch time in .datetime() item method:
>
> =# SELECT jsonb '1519649957.37' @* '$.datetime()';
> ?column?
> --------------------------------
> "2018-02-26T12:59:17.37+00:00"
> (1 row)
>
Documentation in user-friendly format (it will be convered to xml, of
course) is available
https://github.com/obartunov/sqljsondoc/blob/master/README.jsonpath.md
We are permanently working on it.
>
> --
> Nikita Glukhov
> Postgres Professional:http://www.postgrespro.com
> The Russian Postgres Company
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-02-28 03:55 Robert Haas <robertmhaas@gmail.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
2 siblings, 1 reply; 198+ messages in thread
From: Robert Haas @ 2018-02-28 03:55 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
<n.gluhov@postgrespro.ru> wrote:
> Attached 10th version of the jsonpath patches.
>
> 1. Fixed error handling in arithmetic operators.
>
> Now run-time errors in arithmetic operators are catched (added
> PG_TRY/PG_CATCH around operator's functions calls) and converted into
> Unknown values in predicates as it is required by the standard:
I think we really need to rename PG_TRY and PG_CATCH or rethink this
whole interface so that people stop thinking they can use it to
prevent errors from being thrown.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-01 21:40 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Robert Haas <robertmhaas@gmail.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-03-01 21:40 UTC (permalink / raw)
To: Robert Haas <robertmhaas@gmail.com>; +Cc: Oleg Bartunov <obartunov@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 28.02.2018 06:55, Robert Haas wrote:
> On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
> <n.gluhov@postgrespro.ru> wrote:
>> Attached 10th version of the jsonpath patches.
>>
>> 1. Fixed error handling in arithmetic operators.
>>
>> Now run-time errors in arithmetic operators are catched (added
>> PG_TRY/PG_CATCH around operator's functions calls) and converted into
>> Unknown values in predicates as it is required by the standard:
> I think we really need to rename PG_TRY and PG_CATCH or rethink this
> whole interface so that people stop thinking they can use it to
> prevent errors from being thrown.
I understand that it is unsafe to call arbitrary function inside PG_TRY without
rethrowing of caught errors in PG_CATCH, but in jsonpath only the following
numeric and datetime functions with known behavior are called inside PG_TRY
and only errors of category ERRCODE_DATA_EXCEPTION are caught:
numeric_add()
numeric_mul()
numeric_div()
numeric_mod()
numeric_float8()
float8in()
float8_numeric()
to_datetime()
SQL/JSON standard requires us to handle errors and then perform the specified
ON ERROR behavior. In the next SQL/JSON patch I had to use subtransactions for
catching errors in JSON_VALUE, JSON_QUERY and JSON_EXISTS where an arbitrary
user-defined typecast function can be called.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-01 21:57 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2018-03-01 21:57 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Fri, Mar 2, 2018 at 12:40 AM, Nikita Glukhov <n.gluhov@postgrespro.ru>
wrote:
> On 28.02.2018 06:55, Robert Haas wrote:
>
> On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
>> <n.gluhov@postgrespro.ru> wrote:
>>
>>> Attached 10th version of the jsonpath patches.
>>>
>>> 1. Fixed error handling in arithmetic operators.
>>>
>>> Now run-time errors in arithmetic operators are catched (added
>>> PG_TRY/PG_CATCH around operator's functions calls) and converted into
>>> Unknown values in predicates as it is required by the standard:
>>>
>> I think we really need to rename PG_TRY and PG_CATCH or rethink this
>> whole interface so that people stop thinking they can use it to
>> prevent errors from being thrown.
>>
>
> I understand that it is unsafe to call arbitrary function inside PG_TRY
> without
> rethrowing of caught errors in PG_CATCH, but in jsonpath only the following
> numeric and datetime functions with known behavior are called inside PG_TRY
> and only errors of category ERRCODE_DATA_EXCEPTION are caught:
>
> numeric_add()
> numeric_mul()
> numeric_div()
> numeric_mod()
> numeric_float8()
> float8in()
> float8_numeric()
> to_datetime()
>
That seems like a quite limited list of functions. What about reworking
them
providing a way of calling them without risk of exception? For example, we
can
have numeric_add_internal() function which fill given data structure with
error information instead of throwing the error. numeric_add() would be a
wrapper over numeric_add_internal(), which throws an error if corresponding
data structure is filled. In jsonpath we can call numeric_add_internal()
and
interpret errors in another way. That seems to be better than use of PG_TRY
and PG_CATCH.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-05 21:29 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
2 siblings, 2 replies; 198+ messages in thread
From: Tomas Vondra @ 2018-03-05 21:29 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; obartunov@gmail.com; +Cc: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Hi,
The patch no longer applies - it got broken by fd1a421fe66 which changed
columns in pg_proc. A rebase is needed.
Fixing it is pretty simle, so I've done that locally and tried to run
'make check' under valgrind. And I got a bunch of reports about
uninitialised values. Full report attached, but in general there seem to
be two types of failures:
Conditional jump or move depends on uninitialised value(s)
at 0x57BB47A: vfprintf (in /usr/lib64/libc-2.24.so)
by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
by 0xA926D3: pvsnprintf (psprintf.c:121)
by 0x723E03: appendStringInfoVA (stringinfo.c:130)
by 0x723D58: appendStringInfo (stringinfo.c:87)
by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
by 0x776F99: outNode (outfuncs.c:3978)
by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
by 0x777CB9: outNode (outfuncs.c:4398)
by 0x76D507: _outJsonExpr (outfuncs.c:1752)
by 0x777CA1: outNode (outfuncs.c:4395)
by 0x767000: _outList (outfuncs.c:187)
by 0x776874: outNode (outfuncs.c:3753)
by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
by 0x776D89: outNode (outfuncs.c:3912)
by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
by 0x777959: outNode (outfuncs.c:4290)
by 0x767000: _outList (outfuncs.c:187)
by 0x776874: outNode (outfuncs.c:3753)
by 0x773713: _outQuery (outfuncs.c:3049)
Uninitialised value was created by a stack allocation
at 0x5B0C19: base_yyparse (gram.c:26287)
This happens when _outCoerceViaIO tries to output 'location' field
(that's line 1413), so I guess it's not set/copied somewhere.
The second failure looks like this:
Conditional jump or move depends on uninitialised value(s)
at 0x49E58B: ginFillScanEntry (ginscan.c:72)
by 0x49EB56: ginFillScanKey (ginscan.c:221)
by 0x49EF72: ginNewScanKey (ginscan.c:369)
by 0x4A3875: gingetbitmap (ginget.c:1807)
by 0x4F620B: index_getbitmap (indexam.c:727)
by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
by 0x6DC26D: ExecScanFetch (execScan.c:95)
by 0x6DC308: ExecScan (execScan.c:162)
by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
by 0x6E5961: ExecProcNode (executor.h:239)
by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
by 0x6D1361: ExecProcNode (executor.h:239)
by 0x6D3BB7: ExecutePlan (execMain.c:1721)
by 0x6D1917: standard_ExecutorRun (execMain.c:361)
Uninitialised value was created by a heap allocation
at 0xA64FDC: palloc (mcxt.c:858)
by 0x938636: gin_extract_jsonpath_query (jsonb_gin.c:630)
by 0x938AB6: gin_extract_jsonb_query (jsonb_gin.c:746)
by 0xA340C0: FunctionCall7Coll (fmgr.c:1201)
by 0x49EE7F: ginNewScanKey (ginscan.c:313)
by 0x4A3875: gingetbitmap (ginget.c:1807)
by 0x4F620B: index_getbitmap (indexam.c:727)
by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
by 0x6DC26D: ExecScanFetch (execScan.c:95)
by 0x6DC308: ExecScan (execScan.c:162)
by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
by 0x6E5961: ExecProcNode (executor.h:239)
by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
by 0x6D1361: ExecProcNode (executor.h:239)
So the extra_data allocated in gin_extract_jsonpath_query() get to
ginFillScanEntry() uninitialised.
Both seem like a valid issues, I think.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
valgrind.log (text/x-log, 23.6K) download
==31228== Conditional jump or move depends on uninitialised value(s)
==31228== at 0x57BB47A: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x773713: _outQuery (outfuncs.c:3049)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31228== Use of uninitialised value of size 8
==31228== at 0x57B7D8B: _itoa_word (in /usr/lib64/libc-2.24.so)
==31228== by 0x57BC542: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Value8
fun:_itoa_word
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
}
==31228== Conditional jump or move depends on uninitialised value(s)
==31228== at 0x57B7D95: _itoa_word (in /usr/lib64/libc-2.24.so)
==31228== by 0x57BC542: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:_itoa_word
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
}
==31228== Conditional jump or move depends on uninitialised value(s)
==31228== at 0x57BC659: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x773713: _outQuery (outfuncs.c:3049)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31228== Conditional jump or move depends on uninitialised value(s)
==31228== at 0x57BB526: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x773713: _outQuery (outfuncs.c:3049)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31228== Conditional jump or move depends on uninitialised value(s)
==31228== at 0x57BB5A9: vfprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31228== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31228== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31228== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31228== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31228== by 0x776F99: outNode (outfuncs.c:3978)
==31228== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31228== by 0x777CB9: outNode (outfuncs.c:4398)
==31228== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31228== by 0x777CA1: outNode (outfuncs.c:4395)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31228== by 0x776D89: outNode (outfuncs.c:3912)
==31228== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31228== by 0x777959: outNode (outfuncs.c:4290)
==31228== by 0x767000: _outList (outfuncs.c:187)
==31228== by 0x776874: outNode (outfuncs.c:3753)
==31228== by 0x773713: _outQuery (outfuncs.c:3049)
==31228== Uninitialised value was created by a stack allocation
==31228== at 0x5B0C19: base_yyparse (gram.c:26287)
==31228==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31224== Conditional jump or move depends on uninitialised value(s)
==31224== at 0x57BB47A: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x773713: _outQuery (outfuncs.c:3049)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31224== Use of uninitialised value of size 8
==31224== at 0x57B7D8B: _itoa_word (in /usr/lib64/libc-2.24.so)
==31224== by 0x57BC542: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Value8
fun:_itoa_word
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
}
==31224== Conditional jump or move depends on uninitialised value(s)
==31224== at 0x57B7D95: _itoa_word (in /usr/lib64/libc-2.24.so)
==31224== by 0x57BC542: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:_itoa_word
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
}
==31224== Conditional jump or move depends on uninitialised value(s)
==31224== at 0x57BC659: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x773713: _outQuery (outfuncs.c:3049)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31224== Conditional jump or move depends on uninitialised value(s)
==31224== at 0x57BB526: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x773713: _outQuery (outfuncs.c:3049)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31224== Conditional jump or move depends on uninitialised value(s)
==31224== at 0x57BB5A9: vfprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
==31224== by 0xA926D3: pvsnprintf (psprintf.c:121)
==31224== by 0x723E03: appendStringInfoVA (stringinfo.c:130)
==31224== by 0x723D58: appendStringInfo (stringinfo.c:87)
==31224== by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
==31224== by 0x776F99: outNode (outfuncs.c:3978)
==31224== by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
==31224== by 0x777CB9: outNode (outfuncs.c:4398)
==31224== by 0x76D507: _outJsonExpr (outfuncs.c:1752)
==31224== by 0x777CA1: outNode (outfuncs.c:4395)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
==31224== by 0x776D89: outNode (outfuncs.c:3912)
==31224== by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
==31224== by 0x777959: outNode (outfuncs.c:4290)
==31224== by 0x767000: _outList (outfuncs.c:187)
==31224== by 0x776874: outNode (outfuncs.c:3753)
==31224== by 0x773713: _outQuery (outfuncs.c:3049)
==31224== Uninitialised value was created by a stack allocation
==31224== at 0x5B0C19: base_yyparse (gram.c:26287)
==31224==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:vfprintf
fun:vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:appendStringInfo
fun:_outCoerceViaIO
fun:outNode
fun:_outJsonCoercion
fun:outNode
fun:_outJsonExpr
fun:outNode
fun:_outList
fun:outNode
fun:_outTableFunc
fun:outNode
fun:_outRangeTblEntry
fun:outNode
fun:_outList
fun:outNode
fun:_outQuery
}
==31221== Conditional jump or move depends on uninitialised value(s)
==31221== at 0x49E58B: ginFillScanEntry (ginscan.c:72)
==31221== by 0x49EB56: ginFillScanKey (ginscan.c:221)
==31221== by 0x49EF72: ginNewScanKey (ginscan.c:369)
==31221== by 0x4A3875: gingetbitmap (ginget.c:1807)
==31221== by 0x4F620B: index_getbitmap (indexam.c:727)
==31221== by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
==31221== by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
==31221== by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
==31221== by 0x6DC26D: ExecScanFetch (execScan.c:95)
==31221== by 0x6DC308: ExecScan (execScan.c:162)
==31221== by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
==31221== by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
==31221== by 0x6E5961: ExecProcNode (executor.h:239)
==31221== by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
==31221== by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
==31221== by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
==31221== by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
==31221== by 0x6D1361: ExecProcNode (executor.h:239)
==31221== by 0x6D3BB7: ExecutePlan (execMain.c:1721)
==31221== by 0x6D1917: standard_ExecutorRun (execMain.c:361)
==31221== Uninitialised value was created by a heap allocation
==31221== at 0xA64FDC: palloc (mcxt.c:858)
==31221== by 0x938636: gin_extract_jsonpath_query (jsonb_gin.c:630)
==31221== by 0x938AB6: gin_extract_jsonb_query (jsonb_gin.c:746)
==31221== by 0xA340C0: FunctionCall7Coll (fmgr.c:1201)
==31221== by 0x49EE7F: ginNewScanKey (ginscan.c:313)
==31221== by 0x4A3875: gingetbitmap (ginget.c:1807)
==31221== by 0x4F620B: index_getbitmap (indexam.c:727)
==31221== by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
==31221== by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
==31221== by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
==31221== by 0x6DC26D: ExecScanFetch (execScan.c:95)
==31221== by 0x6DC308: ExecScan (execScan.c:162)
==31221== by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
==31221== by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
==31221== by 0x6E5961: ExecProcNode (executor.h:239)
==31221== by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
==31221== by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
==31221== by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
==31221== by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
==31221== by 0x6D1361: ExecProcNode (executor.h:239)
==31221==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:ginFillScanEntry
fun:ginFillScanKey
fun:ginNewScanKey
fun:gingetbitmap
fun:index_getbitmap
fun:MultiExecBitmapIndexScan
fun:MultiExecProcNode
fun:BitmapHeapNext
fun:ExecScanFetch
fun:ExecScan
fun:ExecBitmapHeapScan
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:fetch_input_tuple
fun:agg_retrieve_direct
fun:ExecAgg
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
}
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-05 22:56 Oleg Bartunov <obartunov@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Oleg Bartunov @ 2018-03-05 22:56 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Thanks, Tomas !
Will publish a new version really soon !
Regards,
Oleg
On Tue, Mar 6, 2018 at 12:29 AM, Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> Hi,
>
> The patch no longer applies - it got broken by fd1a421fe66 which changed
> columns in pg_proc. A rebase is needed.
>
> Fixing it is pretty simle, so I've done that locally and tried to run
> 'make check' under valgrind. And I got a bunch of reports about
> uninitialised values. Full report attached, but in general there seem to
> be two types of failures:
>
> Conditional jump or move depends on uninitialised value(s)
> at 0x57BB47A: vfprintf (in /usr/lib64/libc-2.24.so)
> by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
> by 0xA926D3: pvsnprintf (psprintf.c:121)
> by 0x723E03: appendStringInfoVA (stringinfo.c:130)
> by 0x723D58: appendStringInfo (stringinfo.c:87)
> by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
> by 0x776F99: outNode (outfuncs.c:3978)
> by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
> by 0x777CB9: outNode (outfuncs.c:4398)
> by 0x76D507: _outJsonExpr (outfuncs.c:1752)
> by 0x777CA1: outNode (outfuncs.c:4395)
> by 0x767000: _outList (outfuncs.c:187)
> by 0x776874: outNode (outfuncs.c:3753)
> by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
> by 0x776D89: outNode (outfuncs.c:3912)
> by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
> by 0x777959: outNode (outfuncs.c:4290)
> by 0x767000: _outList (outfuncs.c:187)
> by 0x776874: outNode (outfuncs.c:3753)
> by 0x773713: _outQuery (outfuncs.c:3049)
> Uninitialised value was created by a stack allocation
> at 0x5B0C19: base_yyparse (gram.c:26287)
>
> This happens when _outCoerceViaIO tries to output 'location' field
> (that's line 1413), so I guess it's not set/copied somewhere.
>
> The second failure looks like this:
>
> Conditional jump or move depends on uninitialised value(s)
> at 0x49E58B: ginFillScanEntry (ginscan.c:72)
> by 0x49EB56: ginFillScanKey (ginscan.c:221)
> by 0x49EF72: ginNewScanKey (ginscan.c:369)
> by 0x4A3875: gingetbitmap (ginget.c:1807)
> by 0x4F620B: index_getbitmap (indexam.c:727)
> by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
> by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
> by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
> by 0x6DC26D: ExecScanFetch (execScan.c:95)
> by 0x6DC308: ExecScan (execScan.c:162)
> by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6E5961: ExecProcNode (executor.h:239)
> by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
> by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
> by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6D1361: ExecProcNode (executor.h:239)
> by 0x6D3BB7: ExecutePlan (execMain.c:1721)
> by 0x6D1917: standard_ExecutorRun (execMain.c:361)
> Uninitialised value was created by a heap allocation
> at 0xA64FDC: palloc (mcxt.c:858)
> by 0x938636: gin_extract_jsonpath_query (jsonb_gin.c:630)
> by 0x938AB6: gin_extract_jsonb_query (jsonb_gin.c:746)
> by 0xA340C0: FunctionCall7Coll (fmgr.c:1201)
> by 0x49EE7F: ginNewScanKey (ginscan.c:313)
> by 0x4A3875: gingetbitmap (ginget.c:1807)
> by 0x4F620B: index_getbitmap (indexam.c:727)
> by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
> by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
> by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
> by 0x6DC26D: ExecScanFetch (execScan.c:95)
> by 0x6DC308: ExecScan (execScan.c:162)
> by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6E5961: ExecProcNode (executor.h:239)
> by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
> by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
> by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6D1361: ExecProcNode (executor.h:239)
>
> So the extra_data allocated in gin_extract_jsonpath_query() get to
> ginFillScanEntry() uninitialised.
>
>
> Both seem like a valid issues, I think.
>
> regards
>
> --
> Tomas Vondra http://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-05 23:48 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Nikita Glukhov @ 2018-03-05 23:48 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; obartunov@gmail.com; +Cc: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 06.03.2018 00:29, Tomas Vondra wrote:
> Hi,
>
> The patch no longer applies - it got broken by fd1a421fe66 which changed
> columns in pg_proc. A rebase is needed.
>
> Fixing it is pretty simle, so I've done that locally and tried to run
> 'make check' under valgrind. And I got a bunch of reports about
> uninitialised values.
Thank you very much for your discovery. It is my fault that I never ran
PostgreSQL under valgrind. Rebased and fixed patches are attached.
> Full report attached, but in general there seem to
> be two types of failures:
>
> Conditional jump or move depends on uninitialised value(s)
> at 0x57BB47A: vfprintf (in /usr/lib64/libc-2.24.so)
> by 0x57E5478: vsnprintf (in /usr/lib64/libc-2.24.so)
> by 0xA926D3: pvsnprintf (psprintf.c:121)
> by 0x723E03: appendStringInfoVA (stringinfo.c:130)
> by 0x723D58: appendStringInfo (stringinfo.c:87)
> by 0x76BEFF: _outCoerceViaIO (outfuncs.c:1413)
> by 0x776F99: outNode (outfuncs.c:3978)
> by 0x76D7E7: _outJsonCoercion (outfuncs.c:1779)
> by 0x777CB9: outNode (outfuncs.c:4398)
> by 0x76D507: _outJsonExpr (outfuncs.c:1752)
> by 0x777CA1: outNode (outfuncs.c:4395)
> by 0x767000: _outList (outfuncs.c:187)
> by 0x776874: outNode (outfuncs.c:3753)
> by 0x76A4D2: _outTableFunc (outfuncs.c:1068)
> by 0x776D89: outNode (outfuncs.c:3912)
> by 0x7744FD: _outRangeTblEntry (outfuncs.c:3209)
> by 0x777959: outNode (outfuncs.c:4290)
> by 0x767000: _outList (outfuncs.c:187)
> by 0x776874: outNode (outfuncs.c:3753)
> by 0x773713: _outQuery (outfuncs.c:3049)
> Uninitialised value was created by a stack allocation
> at 0x5B0C19: base_yyparse (gram.c:26287)
>
> This happens when _outCoerceViaIO tries to output 'location' field
> (that's line 1413), so I guess it's not set/copied somewhere.
Yes, JSON FORMAT location was not set in gram.y.
> The second failure looks like this:
>
> Conditional jump or move depends on uninitialised value(s)
> at 0x49E58B: ginFillScanEntry (ginscan.c:72)
> by 0x49EB56: ginFillScanKey (ginscan.c:221)
> by 0x49EF72: ginNewScanKey (ginscan.c:369)
> by 0x4A3875: gingetbitmap (ginget.c:1807)
> by 0x4F620B: index_getbitmap (indexam.c:727)
> by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
> by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
> by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
> by 0x6DC26D: ExecScanFetch (execScan.c:95)
> by 0x6DC308: ExecScan (execScan.c:162)
> by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6E5961: ExecProcNode (executor.h:239)
> by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
> by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
> by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6D1361: ExecProcNode (executor.h:239)
> by 0x6D3BB7: ExecutePlan (execMain.c:1721)
> by 0x6D1917: standard_ExecutorRun (execMain.c:361)
> Uninitialised value was created by a heap allocation
> at 0xA64FDC: palloc (mcxt.c:858)
> by 0x938636: gin_extract_jsonpath_query (jsonb_gin.c:630)
> by 0x938AB6: gin_extract_jsonb_query (jsonb_gin.c:746)
> by 0xA340C0: FunctionCall7Coll (fmgr.c:1201)
> by 0x49EE7F: ginNewScanKey (ginscan.c:313)
> by 0x4A3875: gingetbitmap (ginget.c:1807)
> by 0x4F620B: index_getbitmap (indexam.c:727)
> by 0x6EE342: MultiExecBitmapIndexScan (nodeBitmapIndexscan.c:104)
> by 0x6DA8F8: MultiExecProcNode (execProcnode.c:506)
> by 0x6EC53D: BitmapHeapNext (nodeBitmapHeapscan.c:118)
> by 0x6DC26D: ExecScanFetch (execScan.c:95)
> by 0x6DC308: ExecScan (execScan.c:162)
> by 0x6ED7E5: ExecBitmapHeapScan (nodeBitmapHeapscan.c:730)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6E5961: ExecProcNode (executor.h:239)
> by 0x6E5E25: fetch_input_tuple (nodeAgg.c:406)
> by 0x6E8091: agg_retrieve_direct (nodeAgg.c:1736)
> by 0x6E7C84: ExecAgg (nodeAgg.c:1551)
> by 0x6DA80A: ExecProcNodeFirst (execProcnode.c:446)
> by 0x6D1361: ExecProcNode (executor.h:239)
>
> So the extra_data allocated in gin_extract_jsonpath_query() get to
> ginFillScanEntry() uninitialised.
Yes, only the first element of extra_data[] was initialized and used later in
gin_consistent_jsonb(), but ginFillScanEntry() wants all of them to be
initialized.
> Both seem like a valid issues, I think.
>
> regards
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v11.tar.gz (application/gzip, 78.6K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-07 14:33 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-03-07 14:33 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 02.03.2018 00:57, Alexander Korotkov wrote:
> On Fri, Mar 2, 2018 at 12:40 AM, Nikita Glukhov
> <n.gluhov@postgrespro.ru <mailto:n.gluhov@postgrespro.ru>> wrote:
>
> On 28.02.2018 06:55, Robert Haas wrote:
>
> On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
> <n.gluhov@postgrespro.ru <mailto:n.gluhov@postgrespro.ru>> wrote:
>
> Attached 10th version of the jsonpath patches.
>
> 1. Fixed error handling in arithmetic operators.
>
> Now run-time errors in arithmetic operators are
> catched (added
> PG_TRY/PG_CATCH around operator's functions calls) and
> converted into
> Unknown values in predicates as it is required by the
> standard:
>
> I think we really need to rename PG_TRY and PG_CATCH or
> rethink this
> whole interface so that people stop thinking they can use it to
> prevent errors from being thrown.
>
>
> I understand that it is unsafe to call arbitrary function inside
> PG_TRY without
> rethrowing of caught errors in PG_CATCH, but in jsonpath only the
> following
> numeric and datetime functions with known behavior are called
> inside PG_TRY
> and only errors of category ERRCODE_DATA_EXCEPTION are caught:
>
> numeric_add()
> numeric_mul()
> numeric_div()
> numeric_mod()
> numeric_float8()
> float8in()
> float8_numeric()
> to_datetime()
>
>
> That seems like a quite limited list of functions. What about
> reworking them
> providing a way of calling them without risk of exception? For
> example, we can
> have numeric_add_internal() function which fill given data structure with
> error information instead of throwing the error. numeric_add() would be a
> wrapper over numeric_add_internal(), which throws an error if
> corresponding
> data structure is filled. In jsonpath we can call
> numeric_add_internal() and
> interpret errors in another way. That seems to be better than use of
> PG_TRY
> and PG_CATCH.
Attached 12th version of jsonpath patches.
I added the 7th patch where the following functions were extracted for
safe error handling in jsonpath:
numeric_add_internal()
numeric_sub_internal()
numeric_mul_internal()
numeric_div_internal()
numeric_mod_internal()
float8_numeric_internal()
numeric_float8_internal()
float8in_internal()
Errors are passed to caller with new ereport_safe() macro when
ErrorData **edata is not NULL:
+#define ereport_safe(edata, elevel, rest) \
+ do { \
+ if (edata) { \
+ errstart(elevel, __FILE__, __LINE__, PG_FUNCNAME_MACRO, TEXTDOMAIN); \
+ (rest); \
+ *(edata) = CopyErrorData(); \
+ FlushErrorState(); \
+ } else { \
+ ereport(elevel, rest); \
+ } \
+ } while (0)
But to_datetime() is still called in jsonpath inside PG_TRY/PG_CATCH block
because it needs too deep error propagation.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v12.tar.gz (application/gzip, 85.9K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-15 17:13 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 0 replies; 198+ messages in thread
From: Nikita Glukhov @ 2018-03-15 17:13 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Attached 13th version of the jsonpath patches.
Syntax of .** accessor (our extension to standard) was changed to become more
similar to the syntax of the standard array accessor:
.**{2, 5} => .**{2 to 5}
.**{3,} => .**{3 to last}
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v13.tar.gz (application/gzip, 86.2K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-20 05:06 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Andrew Dunstan @ 2018-03-20 05:06 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Fri, Mar 2, 2018 at 8:27 AM, Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> On Fri, Mar 2, 2018 at 12:40 AM, Nikita Glukhov <n.gluhov@postgrespro.ru>
> wrote:
>>
>> On 28.02.2018 06:55, Robert Haas wrote:
>>
>>> On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
>>> <n.gluhov@postgrespro.ru> wrote:
>>>>
>>>> Attached 10th version of the jsonpath patches.
>>>>
>>>> 1. Fixed error handling in arithmetic operators.
>>>>
>>>> Now run-time errors in arithmetic operators are catched (added
>>>> PG_TRY/PG_CATCH around operator's functions calls) and converted
>>>> into
>>>> Unknown values in predicates as it is required by the standard:
>>>
>>> I think we really need to rename PG_TRY and PG_CATCH or rethink this
>>> whole interface so that people stop thinking they can use it to
>>> prevent errors from being thrown.
>>
>>
>> I understand that it is unsafe to call arbitrary function inside PG_TRY
>> without
>> rethrowing of caught errors in PG_CATCH, but in jsonpath only the
>> following
>> numeric and datetime functions with known behavior are called inside
>> PG_TRY
>> and only errors of category ERRCODE_DATA_EXCEPTION are caught:
>>
>> numeric_add()
>> numeric_mul()
>> numeric_div()
>> numeric_mod()
>> numeric_float8()
>> float8in()
>> float8_numeric()
>> to_datetime()
>
>
> That seems like a quite limited list of functions. What about reworking
> them
> providing a way of calling them without risk of exception? For example, we
> can
> have numeric_add_internal() function which fill given data structure with
> error information instead of throwing the error. numeric_add() would be a
> wrapper over numeric_add_internal(), which throws an error if corresponding
> data structure is filled. In jsonpath we can call numeric_add_internal()
> and
> interpret errors in another way. That seems to be better than use of PG_TRY
> and PG_CATCH.
>
I haven't seen a response to this email. Do we need one before
proceeding any further with jsonpath?
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-20 05:09 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Andrew Dunstan @ 2018-03-20 05:09 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Tue, Mar 20, 2018 at 3:36 PM, Andrew Dunstan
<andrew.dunstan@2ndquadrant.com> wrote:
> On Fri, Mar 2, 2018 at 8:27 AM, Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
>> On Fri, Mar 2, 2018 at 12:40 AM, Nikita Glukhov <n.gluhov@postgrespro.ru>
>> wrote:
>>>
>>> On 28.02.2018 06:55, Robert Haas wrote:
>>>
>>>> On Mon, Feb 26, 2018 at 10:34 AM, Nikita Glukhov
>>>> <n.gluhov@postgrespro.ru> wrote:
>>>>>
>>>>> Attached 10th version of the jsonpath patches.
>>>>>
>>>>> 1. Fixed error handling in arithmetic operators.
>>>>>
>>>>> Now run-time errors in arithmetic operators are catched (added
>>>>> PG_TRY/PG_CATCH around operator's functions calls) and converted
>>>>> into
>>>>> Unknown values in predicates as it is required by the standard:
>>>>
>>>> I think we really need to rename PG_TRY and PG_CATCH or rethink this
>>>> whole interface so that people stop thinking they can use it to
>>>> prevent errors from being thrown.
>>>
>>>
>>> I understand that it is unsafe to call arbitrary function inside PG_TRY
>>> without
>>> rethrowing of caught errors in PG_CATCH, but in jsonpath only the
>>> following
>>> numeric and datetime functions with known behavior are called inside
>>> PG_TRY
>>> and only errors of category ERRCODE_DATA_EXCEPTION are caught:
>>>
>>> numeric_add()
>>> numeric_mul()
>>> numeric_div()
>>> numeric_mod()
>>> numeric_float8()
>>> float8in()
>>> float8_numeric()
>>> to_datetime()
>>
>>
>> That seems like a quite limited list of functions. What about reworking
>> them
>> providing a way of calling them without risk of exception? For example, we
>> can
>> have numeric_add_internal() function which fill given data structure with
>> error information instead of throwing the error. numeric_add() would be a
>> wrapper over numeric_add_internal(), which throws an error if corresponding
>> data structure is filled. In jsonpath we can call numeric_add_internal()
>> and
>> interpret errors in another way. That seems to be better than use of PG_TRY
>> and PG_CATCH.
>>
>
>
> I haven't seen a response to this email. Do we need one before
> proceeding any further with jsonpath?
>
Apologies. I see the reply now.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-20 05:36 Tom Lane <tgl@sss.pgh.pa.us>
parent: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
1 sibling, 1 reply; 198+ messages in thread
From: Tom Lane @ 2018-03-20 05:36 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Andrew Dunstan <andrew.dunstan@2ndquadrant.com> writes:
>> That seems like a quite limited list of functions. What about
>> reworking them providing a way of calling them without risk of
>> exception?
> I haven't seen a response to this email. Do we need one before
> proceeding any further with jsonpath?
I've not been following this thread in detail, but IMO any code anywhere
that thinks that no error can be thrown out of non-straight-line code is
broken beyond redemption. What, for example, happens if we get ENOMEM
within one of the elog.c functions?
I did look through 0007-jsonpath-arithmetic-error-handling-v12.patch,
and I can't believe that's seriously proposed for commit. It's making
some pretty fragile changes in error handling, and so far as I can
find there is not even one line of commentary as to what the new
design rules are supposed to be. Even if it's completely bug-free
today (which I would bet against), how could we keep it so?
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-03-27 15:13 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-03-27 15:13 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Attached 14th version of the patches:
* refactored predicates, introduced 3-valued JsonPathBool type instead of
JsonPathExecResult
* refactored JsonPathExecResult: now it is typedefed to ErrorData *
* fixed recursive wildcard accessor (.**) in strict mode: structural errors
after .** are ignored now
On 20.03.2018 08:36, Tom Lane wrote:
> Andrew Dunstan <andrew.dunstan@2ndquadrant.com> writes:
>>> That seems like a quite limited list of functions. What about
>>> reworking them providing a way of calling them without risk of
>>> exception?
>> I haven't seen a response to this email. Do we need one before
>> proceeding any further with jsonpath?
> I've not been following this thread in detail, but IMO any code anywhere
> that thinks that no error can be thrown out of non-straight-line code is
> broken beyond redemption. What, for example, happens if we get ENOMEM
> within one of the elog.c functions?
Out of memory are intentionally is not caught here, ereport_safe() is primarily
intended only for handling of errors of ERRCODE_DATA_EXCEPTION category (see
explanation below).
> I did look through 0007-jsonpath-arithmetic-error-handling-v12.patch,
> and I can't believe that's seriously proposed for commit. It's making
> some pretty fragile changes in error handling, and so far as I can
> find there is not even one line of commentary as to what the new
> design rules are supposed to be. Even if it's completely bug-free
> today (which I would bet against), how could we keep it so?
>
> regards, tom lane
I basically agree, this experimental patch is optional (see below).
I think I need to clarify the error handling in SQL/JSON one more time here.
SQL/JSON standard requires us to handle errors being thrown from the JSON path
engine and from SQL/JSON item => SQL type coercions, and then perform the
specified ON ERROR behavior. Standard does not limit handled error categories,
but I think we are interesting here only in errors of category "data exception"
(ERRCODE_DATA_EXCEPTION) in which all possible SQL/JSON, arithmetic, datetime
errors fall. It might be interesting that such error handling has appeared in
standard only with SQL/JSON introduction.
Example of arithmetic error handling in JSON_VALUE:
=# SELECT JSON_VALUE('0', '1 / $' RETURNING int); -- NULL ON ERROR by default
json_value
------------
(1 row)
=# SELECT JSON_VALUE('0', '1 / $' RETURNING int ERROR ON ERROR);
ERROR: division by zero
=# SELECT JSON_VALUE('0', '1 / $' RETURNING int DEFAULT -1 ON ERROR);
json_value
------------
-1
(1 row)
Error handling in SQL/JSON functions is implemented with PG_TRY/PG_CATCH block
with subtransaction (see ExecEvalJsonExpr() in SQL/JSON patch). We had to use
subtransactions here because an arbitrary user-defined typecast function can
be called.
One could think that could only support ERROR ON ERROR behavior and have no
problems with PG_TRY/PG_CATCH, but errors should be handled inside jsonpath
predicates (boolean expression used in filters) regardless of ON ERROR behavior.
Any error in predicate operands is converted directly to special Unknown value
(analogue of SQL NULL).
In the following examples arithmetic errors and errors in conversion of string
into double are handled by comparison predicate itself, not by JSON_QUERY:
=# SELECT JSON_QUERY('[1.23, "4.56", 0, "foo"]',
'$[*] ? (1 / @.double() > 0)' WITH WRAPPER);
json_query
----------------
[1.23, "4.56"]
(1 row)
=# SELECT JSON_QUERY('[1.23, "4.56", 0, "foo"]',
'$[*] ? ((1 / @.double() > 0) is unknown)' WITH WRAPPER);
json_query
------------
[0, "foo"]
(1 row)
Implementation of handling of arithmetic and datetime errors in patch #4 uses
PG_TRY/PG_CATCH without surrounding substransactions, because only the limited
set of built-in functions with known behavior is called inside this block:
numeric_add()
numeric_mul()
numeric_div()
numeric_mod()
numeric_float8()
float8in()
float8_numeric()
to_datetime()
The 7th optional patch is attempt to completely get rid of PG_TRY/PG_CATCH in
jsonpath by the idea of Alexander Korotkov: additional parameter
"ErrorData **edata" is passed to internal variants of those functions for
saving error info into it instead of throwing it. But I agree that this
solution is far from ideal and the assertion that some functions should not
throw exceptions of some category can easily be broken. Moreover, I have done
it only for numeric functions, because to_datetime() function needs too deep
propagation of ErrorData passing.
If your see other variants of implementation of error handling in jsonpath,
please tell us about them. Now we can offer only two variants described above,
but we understand that some future work is necessary to support such standard
features in PostgreSQL without having problems with PG_TRY/PG_CATCH.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v14.tar.gz (application/gzip, 76.5K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-04-10 13:32 David Steele <david@pgmasters.net>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: David Steele @ 2018-04-10 13:32 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 3/27/18 11:13 AM, Nikita Glukhov wrote:
>
> Attached 14th version of the patches:
It appears this entry should be marked Needs Review, so I have done that
and moved it to the next CF.
Regards,
--
-David
david@pgmasters.net
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-06-27 23:38 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: David Steele <david@pgmasters.net>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-06-27 23:38 UTC (permalink / raw)
To: David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
Attached 15th version of the patches.
There are changes only in jsonpath lexer:
* added single-quoted strings
* added escape sequences \v, \', \xXX, \u{...}
It's all required by ECMAScript standard (ECMA-262), from which SQL/JSON
standard adopts lexical rules.
Also, the documentation has been moved into a separate patch
(see https://www.postgresql.org/message-id/732208d3-56c3-25a4-8f08-3be1d54ad51b%40postgrespro.ru).
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v15.tar.gz (application/gzip, 84.2K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-06-28 02:39 Thomas Munro <thomas.munro@enterprisedb.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Thomas Munro @ 2018-06-28 02:39 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On Thu, Jun 28, 2018 at 11:38 AM, Nikita Glukhov
<n.gluhov@postgrespro.ru> wrote:
> Attached 15th version of the patches.
Hi Nikita,
I wonder why the Windows build scripts are not finding and processing
your new .y and .l files:
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.3672
Perhaps Mkvcbuild.pm needs to be told about them? For example, it has this:
$postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
PS If you need a way to test on Windows but don't have a local Windows
build system, this may be useful:
https://wiki.postgresql.org/wiki/Continuous_Integration
--
Thomas Munro
http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-07-02 11:56 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Thomas Munro <thomas.munro@enterprisedb.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-07-02 11:56 UTC (permalink / raw)
To: Thomas Munro <thomas.munro@enterprisedb.com>; +Cc: David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>; pgsql-hackers
On 28.06.2018 05:39, Thomas Munro wrote:
> On Thu, Jun 28, 2018 at 11:38 AM, Nikita Glukhov
> <n.gluhov@postgrespro.ru> wrote:
>> Attached 15th version of the patches.
> Hi Nikita,
>
> I wonder why the Windows build scripts are not finding and processing
> your new .y and .l files:
>
> https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.3672
>
> Perhaps Mkvcbuild.pm needs to be told about them? For example, it has this:
>
> $postgres->AddFiles('src/backend/parser', 'scan.l', 'gram.y');
Yes, generation of jsonpath_gram.* was missing in Mkvcbuild.pm.
I have fixed it in the new 16th version of patches, and now it seems to
work on
Windows (https://ci.appveyor.com/project/NikitaGlukhov/postgres/build/1.0.4)
> PS If you need a way to test on Windows but don't have a local Windows
> build system, this may be useful:
>
> https://wiki.postgresql.org/wiki/Continuous_Integration
Thank, this is really helpful.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v16.tar.gz (application/gzip, 85.1K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-08-22 21:29 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-08-22 21:29 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 17th version of the patches rebased onto the current master.
Nothing significant has changed.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
sqljson_jsonpath_v17.tar.gz (application/gzip, 84.7K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-09-04 14:15 Stas Kelvich <s.kelvich@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Stas Kelvich @ 2018-09-04 14:15 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
> On 23 Aug 2018, at 00:29, Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
>
> Attached 17th version of the patches rebased onto the current master.
>
> Nothing significant has changed.
Hey.
I’ve looked through presented implementation of jsonpath and have some remarks:
1. Current patch set adds functions named jsonpath_* that implementing subset of functionality
of corresponding json_* functions which doesn’t require changes to postgres SQL grammar.
If we are going to commit json_* functions it will be quite strange for end user: two sets of functions
with mostly identical behaviour, except that one of them allows customise error handling and pass variable via
special language constructions. I suggest only leave operators in this patch, but remove functions.
2. Handling of Unknown value differs from one that described in tech report: filter '(@.k == 1)’
doesn’t evaluates to Unknown. Consider following example:
select '42'::json @* '$ ? ( (@.k == 1) is unknown )';
As per my understanding of standard this should show 42. Seems that it was evaluated to False instead,
because boolean operators (! || &&) on filter expression with structural error behave like it is False.
3. .keyvalue() returns object with field named ‘key’ instead of ’name’ as per tech report. ‘key’ field seems to
be more consistent with function name, but i’m not sure it is worths of mismatch with standard. Also ‘id’ field
is omitted, making it harder to use something like GROUP BY afterwards.
4. Looks like jsonpath executor lacks some CHECK_FOR_INTERRUPTS() during hot paths. Backend with
following query is unresponsive to signals:
select count(*) from (
select '[0,1,2,3,4,5,6,7,8,9]'::json @* longpath::jsonpath from (
select '$[' || string_agg(subscripts, ',') ||']' as longpath from (
select 'last,1' as subscripts from generate_series(1,1000000)
) subscripts_q
) jpath_q
) count_q;
5. Files generated by lex/bison should be listed in .gitignore files in corresponding directories.
6. My compiler complains about unused functions: JsonValueListConcat, JsonValueListClear.
7. Presented patch files structure is somewhat complicated with patches to patches. I've melded them
down to following patches:
0001: three first patches with preliminary datetime infrastructure
0002: Jsonpath engine and operators that is your previous 4+6+7
0003: Jsonpath extensions is your previous 8+9
0004: GIN support is your 5th path
Also this patches were formed with 'git format-patch', so one can apply all of them with 'git apply'
restoring each one as commit.
8. Also sending jsonpath_check.sql with queries which I used to check compliance with the standard.
That can be added to regression test, if you want.
--
Stas Kelvich
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company
Attachments: download all (5 files, .tar.gz)
0001-Datetime-infrastructure-for-jsonpath.patch (application/octet-stream, 15.3K) download
From 965ad6c5968797345932140a579c4f76ef299346 Mon Sep 17 00:00:00 2001
From: Stas Kelvich <stas.kelvich@gmail.com>
Date: Tue, 28 Aug 2018 14:14:22 +0300
Subject: [PATCH 1/5] Datetime infrastructure for jsonpath.
---
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 314 +++++++++++++++++++++++++++++++++++--
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, 311 insertions(+), 25 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 87146a2161..bf75332b5b 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
#endif
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
/* common code for timetypmodin and timetztypmodin */
static int32
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1260,7 +1255,7 @@ time_in(PG_FUNCTION_ARGS)
/* tm2time()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
{
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1426,7 +1421,7 @@ time_scale(PG_FUNCTION_ARGS)
* have a fundamental tie together but rather a coincidence of
* implementation. - thomas
*/
-static void
+void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -2004,7 +1999,7 @@ time_part(PG_FUNCTION_ARGS)
/* tm2timetz()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
{
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 30696e3575..9df30f481b 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -947,6 +948,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
@@ -961,7 +966,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);
@@ -978,8 +984,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,
- 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, 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);
@@ -2974,13 +2980,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;
@@ -3262,6 +3270,120 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
+}
+
+/* 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 */
@@ -3563,7 +3685,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3598,7 +3721,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3619,6 +3743,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
*
@@ -3631,14 +3904,20 @@ 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len;
+ char *fmt_tmp = NULL;
char *date_str;
int fmask;
@@ -3649,15 +3928,15 @@ do_to_timestamp(text *date_txt, text *fmt,
*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)
{
/*
@@ -3687,13 +3966,18 @@ 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);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
- pfree(fmt_str);
if (!incache)
pfree(format);
}
+ if (fmt_tmp)
+ pfree(fmt_tmp);
+
DEBUG_TMFC(&tmfc);
/*
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 284e14dd73..5bec100412 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -70,7 +70,6 @@ typedef struct
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
-static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
static void AdjustIntervalForTypmod(Interval *interval, int32 typmod);
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
@@ -330,7 +329,7 @@ timestamp_scale(PG_FUNCTION_ARGS)
* AdjustTimestampForTypmod --- round off a timestamp to suit given typmod
* Works for either timestamp or timestamptz.
*/
-static void
+void
AdjustTimestampForTypmod(Timestamp *time, int32 typmod)
{
static const int64 TimestampScales[MAX_TIMESTAMP_PRECISION + 1] = {
diff --git a/src/include/utils/date.h b/src/include/utils/date.h
index eb6d2a16fe..10cc822752 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -76,5 +76,8 @@ extern TimeTzADT *GetSQLCurrentTime(int32 typmod);
extern TimeADT GetSQLLocalTime(int32 typmod);
extern int time2tm(TimeADT time, struct pg_tm *tm, fsec_t *fsec);
extern int timetz2tm(TimeTzADT *time, struct pg_tm *tm, fsec_t *fsec, int *tzp);
+extern int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
+extern int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
+extern void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
#endif /* DATE_H */
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index d66582b7a2..d3dd851437 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -338,4 +338,6 @@ extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
int n);
extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);
+extern void AdjustTimestampForTypmod(Timestamp *time, int32 typmod);
+
#endif /* DATETIME_H */
diff --git a/src/include/utils/formatting.h b/src/include/utils/formatting.h
index a9f5548b46..208cc000d3 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, const char *fmt, int fmt_len,
+ bool strict, Oid *typid, int32 *typmod);
+
#endif
--
2.15.2 (Apple Git-101.1)
=
0002-Jsonpath-engine-and-operators.patch (application/octet-stream, 340.2K) download
From 1a402d4e9c2f3c07fb5df950f62e6236c0ca550a Mon Sep 17 00:00:00 2001
From: Stas Kelvich <stas.kelvich@gmail.com>
Date: Tue, 28 Aug 2018 14:15:55 +0300
Subject: [PATCH 2/5] Jsonpath engine and operators.
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/Makefile | 25 +-
src/backend/utils/adt/float.c | 48 +-
src/backend/utils/adt/formatting.c | 43 +-
src/backend/utils/adt/json.c | 865 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 874 +++++++++
src/backend/utils/adt/jsonpath_exec.c | 2715 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 492 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 623 ++++++
src/backend/utils/adt/numeric.c | 294 ++-
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 10 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/elog.h | 19 +
src/include/utils/float.h | 7 +-
src/include/utils/formatting.h | 4 +-
src/include/utils/jsonapi.h | 63 +-
src/include/utils/jsonb.h | 34 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 118 ++
src/include/utils/jsonpath_scanner.h | 30 +
src/include/utils/numeric.h | 9 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 800 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
40 files changed, 11785 insertions(+), 188 deletions(-)
create mode 100644 src/backend/utils/adt/jsonpath.c
create mode 100644 src/backend/utils/adt/jsonpath_exec.c
create mode 100644 src/backend/utils/adt/jsonpath_gram.y
create mode 100644 src/backend/utils/adt/jsonpath_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
create mode 100644 src/test/regress/sql/jsonb_jsonpath.sql
create mode 100644 src/test/regress/sql/jsonpath.sql
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 25af514fba..b0f6549a81 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -136,6 +136,9 @@ parser/gram.h: parser/gram.y
storage/lmgr/lwlocknames.h: storage/lmgr/generate-lwlocknames.pl storage/lmgr/lwlocknames.txt
$(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c
+utils/adt/jsonpath_gram.h: utils/adt/jsonpath_gram.y
+ $(MAKE) -C utils/adt jsonpath_gram.h
+
# run this unconditionally to avoid needing to know its dependencies here:
submake-catalog-headers:
$(MAKE) -C catalog distprep generated-header-symlinks
@@ -159,7 +162,7 @@ submake-utils-headers:
.PHONY: generated-headers
-generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h submake-catalog-headers submake-utils-headers
+generated-headers: $(top_builddir)/src/include/parser/gram.h $(top_builddir)/src/include/storage/lwlocknames.h $(top_builddir)/src/include/utils/jsonpath_gram.h submake-catalog-headers submake-utils-headers
$(top_builddir)/src/include/parser/gram.h: parser/gram.h
prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
@@ -171,6 +174,10 @@ $(top_builddir)/src/include/storage/lwlocknames.h: storage/lmgr/lwlocknames.h
cd '$(dir $@)' && rm -f $(notdir $@) && \
$(LN_S) "$$prereqdir/$(notdir $<)" .
+$(top_builddir)/src/include/utils/jsonpath_gram.h: utils/adt/jsonpath_gram.h
+ prereqdir=`cd '$(dir $<)' >/dev/null && pwd` && \
+ cd '$(dir $@)' && rm -f $(notdir $@) && \
+ $(LN_S) "$$prereqdir/$(notdir $<)" .
utils/probes.o: utils/probes.d $(SUBDIROBJS)
$(DTRACE) $(DTRACEFLAGS) -C -G -s $(call expand_subsys,$^) -o $@
@@ -186,6 +193,7 @@ distprep:
$(MAKE) -C replication repl_gram.c repl_scanner.c syncrep_gram.c syncrep_scanner.c
$(MAKE) -C storage/lmgr lwlocknames.h lwlocknames.c
$(MAKE) -C utils distprep
+ $(MAKE) -C utils/adt jsonpath_gram.c jsonpath_gram.h jsonpath_scan.c
$(MAKE) -C utils/misc guc-file.c
$(MAKE) -C utils/sort qsort_tuple.c
@@ -310,6 +318,7 @@ maintainer-clean: distclean
storage/lmgr/lwlocknames.c \
storage/lmgr/lwlocknames.h \
utils/misc/guc-file.c \
+ utils/adt/jsonpath_gram.h \
utils/sort/qsort_tuple.c
diff --git a/src/backend/lib/stringinfo.c b/src/backend/lib/stringinfo.c
index 798a823ac9..da0c098f31 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 4b35dbb8bb..e2ad685b3d 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -17,7 +17,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -32,6 +33,28 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+jsonpath_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index df35557b73..0628ededd3 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -307,7 +307,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
@@ -325,8 +325,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;
@@ -340,10 +340,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);
@@ -416,17 +419,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
@@ -449,10 +456,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/formatting.c b/src/backend/utils/adt/formatting.c
index 9df30f481b..be139a568c 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3749,8 +3749,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;
@@ -3759,6 +3759,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict,
do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags);
*typmod = -1; /* TODO implement FF1, ..., FF9 */
+ *tz = 0;
if (flags & DCH_DATED)
{
@@ -3767,20 +3768,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")));
@@ -3844,20 +3852,27 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict,
if (flags & DCH_ZONED)
{
TimeTzADT *result = palloc(sizeof(TimeTzADT));
- int tz;
if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
{
- int dterr = DecodeTimezone((char *) tm.tm_zone, &tz);
+ int dterr = DecodeTimezone(tzname, tz);
if (dterr)
- DateTimeParseError(dterr, text_to_cstring(date_txt),
- "timetz");
+ DateTimeParseError(dterr, tzname, "timetz");
}
else
- tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
- if (tm2timetz(&tm, fsec, tz, result) != 0)
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timetz out of range")));
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 6f0fe94d63..b19d7b1523 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;
}
@@ -1506,7 +1511,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 +1519,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 +1527,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 +1558,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 +1635,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),
@@ -2553,3 +2577,804 @@ 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,
+ &jbv->val.datetime.tz);
+
+ 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.c b/src/backend/utils/adt/jsonb.c
index 0ae9d7b9c5..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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 3be900f8ee..fd2da1f68f 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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);
@@ -822,6 +822,7 @@ recurse:
/* 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
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,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.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000000..d07423b8ea
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,874 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ bool allowCurrent, bool insideArraySubscript)
+{
+ /* position from begining of jsonpath data */
+ int32 pos = buf->len - JSONPATH_HDRSZ;
+ int32 chld, next;
+
+ check_stack_depth();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? 0 :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld;
+
+ chld = !item->value.args.right ? 0 :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ *(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,
+ allowCurrent,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld;
+ }
+ break;
+ case jpiFilter:
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ item->type == jpiFilter ||
+ allowCurrent,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (!allowCurrent)
+ ereport(ERROR,
+ (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;
+ 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, true);
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ true, true);
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ *(int32*)(buf->data + next) =
+ flattenJsonPathParseItem(buf, item->next, allowCurrent,
+ insideArraySubscript);
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, false, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+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:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000000..d4c6703c72
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2715 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+#ifdef JSONPATH_JSON_C
+#define JSONXOID JSONOID
+#else
+#define JSONXOID JSONBOID
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+#endif
+
+
+typedef struct JsonPathExecContext
+{
+ List *vars;
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+
+static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
+
+
+static inline void
+JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv)
+{
+ if (jvl->singleton)
+ {
+ jvl->list = list_make2(jvl->singleton, jbv);
+ jvl->singleton = NULL;
+ }
+ else if (!jvl->list)
+ jvl->singleton = jbv;
+ else
+ jvl->list = lappend(jvl->list, jbv);
+}
+
+static inline void
+JsonValueListConcat(JsonValueList *jvl1, JsonValueList jvl2)
+{
+ if (jvl1->singleton)
+ {
+ if (jvl2.singleton)
+ jvl1->list = list_make2(jvl1->singleton, jvl2.singleton);
+ else
+ jvl1->list = lcons(jvl1->singleton, jvl2.list);
+
+ jvl1->singleton = NULL;
+ }
+ else if (jvl2.singleton)
+ {
+ if (jvl1->list)
+ jvl1->list = lappend(jvl1->list, jvl2.singleton);
+ else
+ jvl1->singleton = jvl2.singleton;
+ }
+ else if (jvl1->list)
+ jvl1->list = list_concat(jvl1->list, jvl2.list);
+ else
+ jvl1->list = jvl2.list;
+}
+
+static inline int
+JsonValueListLength(const JsonValueList *jvl)
+{
+ return jvl->singleton ? 1 : list_length(jvl->list);
+}
+
+static inline bool
+JsonValueListIsEmpty(JsonValueList *jvl)
+{
+ return !jvl->singleton && list_length(jvl->list) <= 0;
+}
+
+static inline JsonbValue *
+JsonValueListHead(JsonValueList *jvl)
+{
+ return jvl->singleton ? jvl->singleton : linitial(jvl->list);
+}
+
+static inline void
+JsonValueListClear(JsonValueList *jvl)
+{
+ jvl->singleton = NULL;
+ jvl->list = NIL;
+}
+
+static inline List *
+JsonValueListGetList(JsonValueList *jvl)
+{
+ if (jvl->singleton)
+ return list_make1(jvl->singleton);
+
+ return jvl->list;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static void
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+
+ Assert(variable->type == jpiVariable);
+ varName = jspGetString(variable, &varNameLength);
+
+ foreach(cell, vars)
+ {
+ var = (JsonPathVariable*)lfirst(cell);
+
+ if (varNameLength == VARSIZE_ANY_EXHDR(var->varName) &&
+ !strncmp(varName, VARDATA_ANY(var->varName), varNameLength))
+ break;
+
+ var = NULL;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return;
+ }
+
+ switch(var->typid)
+ {
+ case BOOLOID:
+ value->type = jbvBool;
+ value->val.boolean = DatumGetBool(computedValue);
+ break;
+ case NUMERICOID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(computedValue);
+ break;
+ break;
+ case INT2OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int2_numeric, computedValue));
+ break;
+ case INT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int4_numeric, computedValue));
+ break;
+ case INT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ int8_numeric, computedValue));
+ break;
+ case FLOAT4OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case FLOAT8OID:
+ value->type = jbvNumeric;
+ value->val.numeric = DatumGetNumeric(DirectFunctionCall1(
+ float4_numeric, computedValue));
+ break;
+ case TEXTOID:
+ case VARCHAROID:
+ value->type = jbvString;
+ value->val.string.val = VARDATA_ANY(computedValue);
+ value->val.string.len = VARSIZE_ANY_EXHDR(computedValue);
+ break;
+ case 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static void
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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, cxt->vars, value);
+ break;
+ default:
+ elog(ERROR, "Wrong type");
+ }
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Numeric (*func)(Numeric, Numeric, ErrorData **);
+ Numeric res;
+ bool hasNext;
+ ErrorData *edata;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add_internal;
+ break;
+ case jpiSub:
+ func = numeric_sub_internal;
+ break;
+ case jpiMul:
+ func = numeric_mul_internal;
+ break;
+ case jpiDiv:
+ func = numeric_div_internal;
+ break;
+ case jpiMod:
+ func = numeric_mod_internal;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ edata = NULL;
+ res = func(lval->val.numeric, rval->val.numeric, &edata);
+
+ if (edata)
+ return jperMakeErrorData(edata);
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = res;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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 *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, fmt, fmtlen, tzname, strict,
+ typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ 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;
+ }
+
+ JsonbExtractScalar(jb->val.binary.data, v);
+ }
+ else
+ v = jb;
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+ break;
+ }
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ break;
+
+ case jpiIndexArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ int size = JsonbArraySize(jb);
+ bool binary = jb->type == jbvBinary;
+
+ cxt->innermostArraySize = size; /* for LAST evaluation */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ 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;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ {
+ 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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ ErrorData *edata = NULL;
+
+ if (JsonbType(jb) == jbvScalar)
+ jb = JsonbExtractScalar(jb->val.binary.data, &jbv);
+
+ if (jb->type == jbvNumeric)
+ {
+ /* 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 = float8_numeric_internal(val, &edata);
+ }
+ }
+ else
+ {
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ break;
+ }
+
+ if (edata)
+ {
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ThrowErrorData(edata);
+
+ FreeErrorData(edata);
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ else
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ if (tryToParseDatetime(template_str, template_len, datetime,
+ tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ {
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ 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);
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, &obj, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ 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 inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* 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:
+ case jpiIndexArray:
+ jb = wrapItem(jb);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
+ cxt.ignoreStructuralErrors = cxt.laxMode;
+ cxt.root = JsonbInitBinary(&jbv, json);
+ cxt.innermostArraySize = -1;
+
+ if (jspStrictAbsenseOfErrors(&cxt) && !foundJson)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000000..b52038e449
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,492 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/jsonpath.h"
+
+#include "utils/jsonpath_scanner.h"
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+static JsonPathParseItem*
+makeItemType(int type)
+{
+ JsonPathParseItem* v = palloc(sizeof(*v));
+
+ v->type = type;
+ v->next = NULL;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemString(string *s)
+{
+ JsonPathParseItem *v;
+
+ if (s == NULL)
+ {
+ v = makeItemType(jpiNull);
+ }
+ else
+ {
+ v = makeItemType(jpiString);
+ v->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000000..8101ffb265
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,623 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index d8b6921234..b4b8767dde 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 29efb3f6ef..444b2d5997 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index d9b6bad614..91b03890cf 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3336,5 +3336,33 @@
{ 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' },
+{ 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 a14651010f..c523f93bcd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9296,6 +9296,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 48e01cd694..8f2fc29b3b 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -770,6 +770,16 @@
typelem => 'jsonb', typinput => 'array_in', typoutput => 'array_out',
typreceive => 'array_recv', typsend => 'array_send',
typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' },
+{ oid => '6050',
+ typname => 'jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'U',
+ typarray => '_jsonpath', typinput => 'jsonpath_in',
+ typoutput => 'jsonpath_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'x' }
+{ oid => '6051',
+ typname => '_jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'A',
+ typelem => 'jsonpath', typinput => 'array_in', typoutput => 'array_out',
+ typreceive => 'array_recv', typsend => 'array_send',
+ typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' },
{ oid => '2970', descr => 'txid snapshot',
typname => 'txid_snapshot', typlen => '-1', typbyval => 'f',
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 8551237fc6..ff1ecb20ef 100644
--- a/src/include/lib/stringinfo.h
+++ b/src/include/lib/stringinfo.h
@@ -157,4 +157,10 @@ extern void appendBinaryStringInfoNT(StringInfo str,
*/
extern void enlargeStringInfo(StringInfo str, int needed);
+/*------------------------
+ * alignStringInfoInt
+ * Add padding zero bytes to align StringInfo
+ */
+extern void alignStringInfoInt(StringInfo buf);
+
#endif /* STRINGINFO_H */
diff --git a/src/include/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/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/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..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
@@ -161,6 +204,24 @@ 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);
+
+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 27873d4d10..e0199f5f93 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;
@@ -219,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
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -269,6 +273,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 */
@@ -277,11 +283,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,6 +370,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,
@@ -363,6 +380,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);
@@ -379,5 +398,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..73e3f317f0
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000000..a751540116
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_json.h
+ * Jsonpath support for json datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/utils/jsonpath_json.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSONPATH_JSON_H
+#define JSONPATH_JSON_H
+
+/* redefine jsonb structures */
+#define Jsonb Json
+#define JsonbContainer JsonContainer
+#define JsonbIterator JsonIterator
+
+/* redefine jsonb functions */
+#define findJsonbValueFromContainer(jc, flags, jbv) \
+ findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv)
+#define getIthJsonbValueFromContainer(jc, i) \
+ getIthJsonValueFromContainer((JsonContainer *)(jc), i)
+#define pushJsonbValue pushJsonValue
+#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc))
+#define JsonbIteratorNext JsonIteratorNext
+#define JsonbValueToJsonb JsonbValueToJson
+#define JsonbToCString JsonToCString
+#define JsonbUnquote JsonUnquote
+#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv)
+
+/* redefine jsonb macros */
+#undef JsonContainerSize
+#define JsonContainerSize(jc) \
+ ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \
+ JsonContainerIsArray(jc) \
+ ? JsonGetArraySize((JsonContainer *)(jc)) \
+ : ((JsonContainer *)(jc))->header & JB_CMASK)
+
+
+#undef DatumGetJsonbP
+#define DatumGetJsonbP(d) DatumGetJsonP(d)
+
+#undef DatumGetJsonbPCopy
+#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d)
+
+#undef JsonbPGetDatum
+#define JsonbPGetDatum(json) JsonPGetDatum(json)
+
+#undef PG_GETARG_JSONB_P
+#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n))
+
+#undef PG_GETARG_JSONB_P_COPY
+#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n))
+
+#undef PG_RETURN_JSONB_P
+#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json))
+
+
+#ifdef DatumGetJsonb
+#undef DatumGetJsonb
+#define DatumGetJsonb(d) DatumGetJsonbP(d)
+#endif
+
+#ifdef DatumGetJsonbCopy
+#undef DatumGetJsonbCopy
+#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d)
+#endif
+
+#ifdef JsonbGetDatum
+#undef JsonbGetDatum
+#define JsonbGetDatum(json) JsonbPGetDatum(json)
+#endif
+
+#ifdef PG_GETARG_JSONB
+#undef PG_GETARG_JSONB
+#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n)
+#endif
+
+#ifdef PG_GETARG_JSONB_COPY
+#undef PG_GETARG_JSONB_COPY
+#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n)
+#endif
+
+#ifdef PG_RETURN_JSONB
+#undef PG_RETURN_JSONB
+#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json)
+#endif
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000000..1c8447f6bf
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
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_ */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000000..09d7df0c11
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select json '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true && $y == true) ||
+ @ == false && !($x == true && $y == true) ||
+ @ == null && ($x == true && $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x && y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x && y
+--------+--------+--------
+ true | true | true
+ true | false | false
+ true | "null" | null
+ false | true | false
+ false | false | false
+ false | "null" | false
+ "null" | true | null
+ "null" | false | false
+ "null" | "null" | null
+(9 rows)
+
+select
+ x, y,
+ jsonpath_query(
+ json '[true, false, null]',
+ '$[*] ? (@ == true && ($x == true || $y == true) ||
+ @ == false && !($x == true || $y == true) ||
+ @ == null && ($x == true || $y == true) is unknown)',
+ json_build_object('x', x, 'y', y)
+ ) as "x || y"
+from
+ (values (json 'true'), ('false'), ('"null"')) x(x),
+ (values (json 'true'), ('false'), ('"null"')) y(y);
+ x | y | x || y
+--------+--------+--------
+ true | true | true
+ true | false | true
+ true | "null" | true
+ false | true | true
+ false | false | false
+ false | "null" | null
+ "null" | true | true
+ "null" | false | null
+ "null" | "null" | null
+(9 rows)
+
+select json '{"a": 1, "b": 1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1, "b": 1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 1, "b": 1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": -1, "b": 1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 2, "b": 1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"c": {"a": 0, "b": 1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select json '[1,2,0,3]' @* '$[*] ? (2 / @ > 0)';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[1,2,0,3]' @* '$[*] ? ((2 / @ > 0) is unknown)';
+ ?column?
+----------
+ 0
+(1 row)
+
+select json '0' @* '1 / $';
+ERROR: division by zero
+-- unwrapping of operator arguments in lax mode
+select json '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select json '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select json '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select json '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select json '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select json '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select json '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select json '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1,2,3]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select json '[]' @~ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonpath_predicate
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(json '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select json '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select json 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select json 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select json '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select json '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'lax $[*].size()';
+ ?column?
+----------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select json '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select json '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select json '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* '$[*].keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'strict $.keyvalue()';
+ERROR: SQL/JSON object not found
+select json '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]' @* 'lax $.keyvalue()';
+ ?column?
+-------------------------------------
+ {"key": "a", "value": 1}
+ {"key": "b", "value": [1, 2]}
+ {"key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select json 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select json '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select json '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select json '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select json '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select json '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select json '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select json '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select json '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select json '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select json 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select json '"10-03-2017 12:34 +05:20"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select json '"12:34:56 +05:20"' @* '$.datetime("HH24:MI:SS TZH:TZM").type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000000..22f7255b5c
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(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"}}
+(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"}}
+(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"}}
+(3 rows)
+
+select jsonb 'null' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000000..8cd0534195
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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
+-------------
+ null.type()
+(1 row)
+
+select '1.type()'::jsonpath;
+ jsonpath
+----------
+ 1.type()
+(1 row)
+
+select '"aaa".type()'::jsonpath;
+ jsonpath
+--------------
+ "aaa".type()
+(1 row)
+
+select 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < .1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < -.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
+select '$ ? (@.a < +.1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < 0.1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < -0.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
+select '$ ? (@.a < +0.1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < 10.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 10.1)
+(1 row)
+
+select '$ ? (@.a < -10.1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -10.1)
+(1 row)
+
+select '$ ? (@.a < +10.1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 10.1)
+(1 row)
+
+select '$ ? (@.a < 1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < -1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < -10)
+(1 row)
+
+select '$ ? (@.a < +1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < .1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -.1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +.1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 0.1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -0.1e1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +0.1e1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 10.1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < -10.1e1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -101)
+(1 row)
+
+select '$ ? (@.a < +10.1e1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < 1e-1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < -1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -0.1)
+(1 row)
+
+select '$ ? (@.a < +1e-1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 0.1)
+(1 row)
+
+select '$ ? (@.a < .1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < -.1e-1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -0.01)
+(1 row)
+
+select '$ ? (@.a < +.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < 0.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < -0.1e-1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -0.01)
+(1 row)
+
+select '$ ? (@.a < +0.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 0.01)
+(1 row)
+
+select '$ ? (@.a < 10.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 1.01)
+(1 row)
+
+select '$ ? (@.a < -10.1e-1)'::jsonpath;
+ jsonpath
+-------------------
+ $?(@."a" < -1.01)
+(1 row)
+
+select '$ ? (@.a < +10.1e-1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < 1.01)
+(1 row)
+
+select '$ ? (@.a < 1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < -1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < -10)
+(1 row)
+
+select '$ ? (@.a < +1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < 10)
+(1 row)
+
+select '$ ? (@.a < .1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -.1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +.1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 0.1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < -0.1e+1)'::jsonpath;
+ jsonpath
+----------------
+ $?(@."a" < -1)
+(1 row)
+
+select '$ ? (@.a < +0.1e+1)'::jsonpath;
+ jsonpath
+---------------
+ $?(@."a" < 1)
+(1 row)
+
+select '$ ? (@.a < 10.1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
+select '$ ? (@.a < -10.1e+1)'::jsonpath;
+ jsonpath
+------------------
+ $?(@."a" < -101)
+(1 row)
+
+select '$ ? (@.a < +10.1e+1)'::jsonpath;
+ jsonpath
+-----------------
+ $?(@."a" < 101)
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..2e0cd2df8f 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..494ccebe73 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -160,6 +160,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000000..22428ccc5b
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000000..369463d844
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000000..7b546873dd
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::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;
+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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 4543d87d83..4a17d5a9a6 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 7d7ce8b031..1d01d3b6e9 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -336,6 +336,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',
--
2.15.2 (Apple Git-101.1)
=
0003-Jsonpath-syntax-extensions.patch (application/octet-stream, 46.2K) download
From fceb7aa356a484529369c98c0bc0aa1f36db9065 Mon Sep 17 00:00:00 2001
From: Stas Kelvich <stas.kelvich@gmail.com>
Date: Tue, 28 Aug 2018 14:18:41 +0300
Subject: [PATCH 3/5] Jsonpath syntax extensions.
---
src/backend/utils/adt/jsonpath.c | 153 +++++++++-
src/backend/utils/adt/jsonpath_exec.c | 398 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 ++++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 +++++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 +++++
src/test/regress/sql/json_jsonpath.sql | 47 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1231 insertions(+), 78 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index d07423b8ea..e92600d5d2 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -132,12 +132,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
item->type == jpiFilter ||
allowCurrent,
@@ -215,6 +218,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 pos =
+ flattenJsonPathParseItem(buf, lfirst(lc),
+ allowCurrent, insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ allowCurrent,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ allowCurrent,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos;
+ ppos[1] = valpos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -300,6 +358,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -489,12 +549,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -558,6 +618,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -580,7 +688,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -691,6 +799,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -702,6 +811,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -716,7 +835,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -768,7 +888,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -872,3 +995,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index d4c6703c72..a4e0a01fe1 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -63,6 +63,8 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1705,9 +1707,127 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else if (JsonbType(jb) == jbvObject)
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+ JsonbValue *wrapped = NULL;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (!wrapped)
+ wrapped = wrapItem(jb);
+
+ res = getArrayIndex(cxt, &from, wrapped, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, wrapped, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else
+ {
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -1982,7 +2102,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -1993,84 +2112,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- if (tryToParseDatetime(template_str, template_len, datetime,
- tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tzname)
- pfree(tzname);
- }
- else
- {
- const char *templates[] = {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- int i;
-
- for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ if (tryToParseDatetime(template_str, template_len,
+ datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (tryToParseDatetime(templates[i], -1, datetime,
- NULL, true, &value, &typid,
- &typmod, &tz))
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
{
- res = jperOk;
- break;
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2173,6 +2321,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
@@ -2308,7 +2583,6 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
return recursiveExecuteUnwrap(cxt, jsp, jb, found);
case jpiAnyArray:
- case jpiIndexArray:
jb = wrapItem(jb);
break;
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index b52038e449..c38a7150b1 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -252,6 +252,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -285,9 +305,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -311,7 +331,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -324,6 +344,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -378,6 +412,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 73e3f317f0..664c55d986 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -82,6 +82,10 @@ typedef enum JsonPathItemType {
jpiLast,
jpiStartsWith,
jpiLikeRegex,
+ jpiSequence,
+ jpiArray,
+ jpiObject,
+ jpiObjectField,
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -135,6 +139,19 @@ typedef struct JsonPathItem {
uint32 last;
} anybounds;
+ struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
@@ -162,6 +179,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -207,6 +227,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 09d7df0c11..0afcefa98a 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 22f7255b5c..a7c19fb5b2 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 8cd0534195..1cf0dc3ac1 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 22428ccc5b..736a8b6e99 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 369463d844..cf0e506fd5 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to @.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 7b546873dd..cb1688b657 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.15.2 (Apple Git-101.1)
=
0004-Jsonpath-gin-indexing-support.patch (application/octet-stream, 42.9K) download
From 171e15ab23f0fc63c1d8f522db3474b46be4df6f Mon Sep 17 00:00:00 2001
From: Stas Kelvich <stas.kelvich@gmail.com>
Date: Tue, 28 Aug 2018 14:16:24 +0300
Subject: [PATCH 4/5] Jsonpath gin indexing support.
---
src/backend/utils/adt/jsonb_gin.c | 765 ++++++++++++++++++++++++++++---
src/include/catalog/pg_amop.dat | 12 +
src/include/utils/jsonb.h | 3 +
src/include/utils/jsonpath.h | 2 +
src/test/regress/expected/jsonb.out | 453 ++++++++++++++++++
src/test/regress/expected/opr_sanity.out | 4 +-
src/test/regress/sql/jsonb.sql | 79 ++++
7 files changed, 1242 insertions(+), 76 deletions(-)
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index c8a27451d2..c11960c03b 100644
--- a/src/backend/utils/adt/jsonb_gin.c
+++ b/src/backend/utils/adt/jsonb_gin.c
@@ -13,6 +13,7 @@
*/
#include "postgres.h"
+#include "miscadmin.h"
#include "access/gin.h"
#include "access/hash.h"
#include "access/stratnum.h"
@@ -20,6 +21,7 @@
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/jsonb.h"
+#include "utils/jsonpath.h"
#include "utils/varlena.h"
typedef struct PathHashStack
@@ -28,9 +30,140 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+typedef enum { eOr, eAnd, eEntry } JsonPathNodeType;
+
+typedef struct JsonPathNode
+{
+ JsonPathNodeType type;
+ union
+ {
+ int nargs;
+ int entryIndex;
+ Datum entryDatum;
+ } val;
+ struct JsonPathNode *args[FLEXIBLE_ARRAY_MEMBER];
+} JsonPathNode;
+
+typedef struct GinEntries
+{
+ Datum *buf;
+ int count;
+ int allocated;
+} GinEntries;
+
+typedef struct ExtractedPathEntry
+{
+ struct ExtractedPathEntry *parent;
+ Datum entry;
+ JsonPathItemType type;
+} ExtractedPathEntry;
+
+typedef union ExtractedJsonPath
+{
+ ExtractedPathEntry *entries;
+ uint32 hash;
+} ExtractedJsonPath;
+
+typedef struct JsonPathExtractionContext
+{
+ ExtractedJsonPath (*addKey)(ExtractedJsonPath path, char *key, int len);
+ JsonPath *indexedPaths;
+ bool pathOps;
+ bool lax;
+} JsonPathExtractionContext;
+
+
static Datum make_text_key(char flag, const char *str, int len);
static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key);
+static JsonPathNode *gin_extract_jsonpath_expr(JsonPathExtractionContext *cxt,
+ JsonPathItem *jsp, ExtractedJsonPath path, bool not);
+
+
+static void
+gin_entries_init(GinEntries *list, int preallocated)
+{
+ list->allocated = preallocated;
+ list->buf = (Datum *) palloc(sizeof(Datum) * list->allocated);
+ list->count = 0;
+}
+
+static int
+gin_entries_add(GinEntries *list, Datum entry)
+{
+ int id = list->count;
+
+ if (list->count >= list->allocated)
+ {
+
+ if (list->allocated)
+ {
+ list->allocated *= 2;
+ list->buf = (Datum *) repalloc(list->buf,
+ sizeof(Datum) * list->allocated);
+ }
+ else
+ {
+ list->allocated = 8;
+ list->buf = (Datum *) palloc(sizeof(Datum) * list->allocated);
+ }
+ }
+
+ list->buf[list->count++] = entry;
+
+ return id;
+}
+
+/* Append key name to a path. */
+static ExtractedJsonPath
+gin_jsonb_ops_add_key(ExtractedJsonPath path, char *key, int len)
+{
+ ExtractedPathEntry *pentry = palloc(sizeof(*pentry));
+
+ pentry->parent = path.entries;
+
+ if (key)
+ {
+ pentry->entry = make_text_key(JGINFLAG_KEY, key, len);
+ pentry->type = jpiKey;
+ }
+ else
+ {
+ pentry->entry = PointerGetDatum(NULL);
+ pentry->type = len;
+ }
+
+ path.entries = pentry;
+
+ return path;
+}
+
+/* Combine existing path hash with next key hash. */
+static ExtractedJsonPath
+gin_jsonb_path_ops_add_key(ExtractedJsonPath path, char *key, int len)
+{
+ if (key)
+ {
+ JsonbValue jbv;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = key;
+ jbv.val.string.len = len;
+
+ JsonbHashScalarValue(&jbv, &path.hash);
+ }
+
+ return path;
+}
+
+static void
+gin_jsonpath_init_context(JsonPathExtractionContext *cxt, bool pathOps, bool lax)
+{
+ cxt->addKey = pathOps ? gin_jsonb_path_ops_add_key : gin_jsonb_ops_add_key;
+ cxt->pathOps = pathOps;
+ cxt->lax = lax;
+}
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -68,12 +201,11 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
{
Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
- int total = 2 * JB_ROOT_COUNT(jb);
+ int total = JB_ROOT_COUNT(jb);
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken r;
- int i = 0;
- Datum *entries;
+ GinEntries entries;
/* If the root level is empty, we certainly have no keys */
if (total == 0)
@@ -83,30 +215,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
/* Otherwise, use 2 * root count as initial estimate of result size */
- entries = (Datum *) palloc(sizeof(Datum) * total);
+ gin_entries_init(&entries, 2 * total);
it = JsonbIteratorInit(&jb->root);
while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
- /* Since we recurse into the object, we might need more space */
- if (i >= total)
- {
- total *= 2;
- entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
- }
-
switch (r)
{
case WJB_KEY:
- entries[i++] = make_scalar_key(&v, true);
+ gin_entries_add(&entries, make_scalar_key(&v, true));
break;
case WJB_ELEM:
/* Pretend string array elements are keys, see jsonb.h */
- entries[i++] = make_scalar_key(&v, (v.type == jbvString));
+ gin_entries_add(&entries, make_scalar_key(&v, v.type == jbvString));
break;
case WJB_VALUE:
- entries[i++] = make_scalar_key(&v, false);
+ gin_entries_add(&entries, make_scalar_key(&v, false));
break;
default:
/* we can ignore structural items */
@@ -114,9 +239,447 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
}
- *nentries = i;
+ *nentries = entries.count;
- PG_RETURN_POINTER(entries);
+ PG_RETURN_POINTER(entries.buf);
+}
+
+
+/*
+ * Extract JSON path into the 'path' with filters.
+ * Returns true iff this path is supported by the index opclass.
+ */
+static bool
+gin_extract_jsonpath_path(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ ExtractedJsonPath *path, List **filters)
+{
+ JsonPathItem next;
+
+ for (;;)
+ {
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ path->entries = NULL; /* reset path */
+ break;
+
+ case jpiCurrent:
+ break;
+
+ case jpiKey:
+ {
+ int keylen;
+ char *key = jspGetString(jsp, &keylen);
+
+ *path = cxt->addKey(*path, key, keylen);
+ break;
+ }
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ *path = cxt->addKey(*path, NULL, jsp->type);
+ break;
+
+ case jpiAny:
+ case jpiAnyKey:
+ if (cxt->pathOps)
+ /* jsonb_path_ops doesn't support wildcard paths */
+ return false;
+
+ *path = cxt->addKey(*path, NULL, jsp->type);
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ JsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = gin_extract_jsonpath_expr(cxt, &arg, *path, false);
+
+ if (filter)
+ *filters = lappend(*filters, filter);
+
+ break;
+ }
+
+ default:
+ /* other path items (like item methods) are not supported */
+ return false;
+ }
+
+ if (!jspGetNext(jsp, &next))
+ break;
+
+ jsp = &next;
+ }
+
+ return true;
+}
+
+/* Append an entry node to the global entry list. */
+static inline JsonPathNode *
+gin_jsonpath_make_entry_node(Datum entry)
+{
+ JsonPathNode *node = palloc(offsetof(JsonPathNode, args));
+
+ node->type = eEntry;
+ node->val.entryDatum = entry;
+
+ return node;
+}
+
+static inline JsonPathNode *
+gin_jsonpath_make_entry_node_scalar(JsonbValue *scalar, bool iskey)
+{
+ return gin_jsonpath_make_entry_node(make_scalar_key(scalar, iskey));
+}
+
+static inline JsonPathNode *
+gin_jsonpath_make_expr_node(JsonPathNodeType type, int nargs)
+{
+ JsonPathNode *node = palloc(offsetof(JsonPathNode, args) +
+ sizeof(node->args[0]) * nargs);
+
+ node->type = type;
+ node->val.nargs = nargs;
+
+ return node;
+}
+
+static inline JsonPathNode *
+gin_jsonpath_make_expr_node_args(JsonPathNodeType type, List *args)
+{
+ JsonPathNode *node = gin_jsonpath_make_expr_node(type, list_length(args));
+ ListCell *lc;
+ int i = 0;
+
+ foreach(lc, args)
+ node->args[i++] = lfirst(lc);
+
+ return node;
+}
+
+static inline JsonPathNode *
+gin_jsonpath_make_expr_node_binary(JsonPathNodeType type,
+ JsonPathNode *arg1, JsonPathNode *arg2)
+{
+ JsonPathNode *node = gin_jsonpath_make_expr_node(type, 2);
+
+ node->args[0] = arg1;
+ node->args[1] = arg2;
+
+ return node;
+}
+
+/*
+ * Extract node from the EXISTS/equality-comparison jsonpath expression. If
+ * 'scalar' is not NULL this is equality-comparsion, otherwise this is
+ * EXISTS-predicate. The current path is passed in 'pathcxt'.
+ */
+static JsonPathNode *
+gin_extract_jsonpath_node(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ ExtractedJsonPath path, JsonbValue *scalar)
+{
+ List *nodes = NIL; /* nodes to be AND-ed */
+
+ /* filters extracted into 'nodes' */
+ if (!gin_extract_jsonpath_path(cxt, jsp, &path, &nodes))
+ return NULL;
+
+ if (cxt->pathOps)
+ {
+ if (scalar)
+ {
+ /* append path hash node for equality queries */
+ uint32 hash = path.hash;
+ JsonPathNode *node;
+
+ JsonbHashScalarValue(scalar, &hash);
+
+ node = gin_jsonpath_make_entry_node(UInt32GetDatum(hash));
+ nodes = lappend(nodes, node);
+ }
+ /* else: jsonb_path_ops doesn't support EXISTS queries */
+ }
+ else
+ {
+ ExtractedPathEntry *pentry;
+
+ /* append path entry nodes */
+ for (pentry = path.entries; pentry; pentry = pentry->parent)
+ {
+ if (pentry->type == jpiKey) /* only keys are indexed */
+ nodes = lappend(nodes,
+ gin_jsonpath_make_entry_node(pentry->entry));
+ }
+
+ if (scalar)
+ {
+ /* append scalar node for equality queries */
+ JsonPathNode *node;
+ ExtractedPathEntry *last = path.entries;
+ GinTernaryValue lastIsArrayAccessor = !last ? GIN_FALSE :
+ last->type == jpiIndexArray ||
+ last->type == jpiAnyArray ? GIN_TRUE :
+ last->type == jpiAny ? GIN_MAYBE : GIN_FALSE;
+
+ /*
+ * Create OR-node when the string scalar can be matched as a key
+ * and a non-key. It is possible in lax mode where arrays are
+ * automatically unwrapped, or in strict mode for jpiAny items.
+ */
+ if (scalar->type == jbvString &&
+ (cxt->lax || lastIsArrayAccessor == GIN_MAYBE))
+ node = gin_jsonpath_make_expr_node_binary(eOr,
+ gin_jsonpath_make_entry_node_scalar(scalar, true),
+ gin_jsonpath_make_entry_node_scalar(scalar, false));
+ else
+ node = gin_jsonpath_make_entry_node_scalar(scalar,
+ scalar->type == jbvString &&
+ lastIsArrayAccessor == GIN_TRUE);
+
+ nodes = lappend(nodes, node);
+ }
+ }
+
+ if (list_length(nodes) <= 0)
+ return NULL; /* need full scan for EXISTS($) queries without filters */
+
+ if (list_length(nodes) == 1)
+ return linitial(nodes); /* avoid extra AND-node */
+
+ /* construct AND-node for path with filters */
+ return gin_jsonpath_make_expr_node_args(eAnd, nodes);
+}
+
+/* Recursively extract nodes from the boolean jsonpath expression. */
+static JsonPathNode *
+gin_extract_jsonpath_expr(JsonPathExtractionContext *cxt, JsonPathItem *jsp,
+ ExtractedJsonPath path, bool not)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd:
+ case jpiOr:
+ {
+ JsonPathItem arg;
+ JsonPathNode *larg;
+ JsonPathNode *rarg;
+ JsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = gin_extract_jsonpath_expr(cxt, &arg, path, not);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = gin_extract_jsonpath_expr(cxt, &arg, path, not);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? eAnd : eOr;
+
+ return gin_jsonpath_make_expr_node_binary(type, larg, rarg);
+ }
+
+ case jpiNot:
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ return gin_extract_jsonpath_expr(cxt, &arg, path, !not);
+ }
+
+ case jpiExists:
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return NULL;
+
+ jspGetArg(jsp, &arg);
+
+ return gin_extract_jsonpath_node(cxt, &arg, path, NULL);
+ }
+
+ case jpiEqual:
+ {
+ JsonPathItem leftItem;
+ JsonPathItem rightItem;
+ JsonPathItem *pathItem;
+ JsonPathItem *scalarItem;
+ JsonbValue scalar;
+
+ if (not)
+ return NULL;
+
+ jspGetLeftArg(jsp, &leftItem);
+ jspGetRightArg(jsp, &rightItem);
+
+ if (jspIsScalar(leftItem.type))
+ {
+ scalarItem = &leftItem;
+ pathItem = &rightItem;
+ }
+ else if (jspIsScalar(rightItem.type))
+ {
+ scalarItem = &rightItem;
+ pathItem = &leftItem;
+ }
+ else
+ return NULL; /* at least one operand should be a scalar */
+
+ switch (scalarItem->type)
+ {
+ case jpiNull:
+ scalar.type = jbvNull;
+ break;
+ case jpiBool:
+ scalar.type = jbvBool;
+ scalar.val.boolean = !!*scalarItem->content.value.data;
+ break;
+ case jpiNumeric:
+ scalar.type = jbvNumeric;
+ scalar.val.numeric =
+ (Numeric) scalarItem->content.value.data;
+ break;
+ case jpiString:
+ scalar.type = jbvString;
+ scalar.val.string.val = scalarItem->content.value.data;
+ scalar.val.string.len = scalarItem->content.value.datalen;
+ break;
+ default:
+ elog(ERROR, "invalid scalar jsonpath item type: %d",
+ scalarItem->type);
+ return NULL;
+ }
+
+ return gin_extract_jsonpath_node(cxt, pathItem, path, &scalar);
+ }
+
+ default:
+ return NULL;
+ }
+}
+
+/* Recursively emit all GIN entries found in the node tree */
+static void
+gin_jsonpath_emit_entries(JsonPathNode *node, GinEntries *entries)
+{
+ check_stack_depth();
+
+ switch (node->type)
+ {
+ case eEntry:
+ /* replace datum with its index in the array */
+ node->val.entryIndex =
+ gin_entries_add(entries, node->val.entryDatum);
+ break;
+
+ case eOr:
+ case eAnd:
+ {
+ int i;
+
+ for (i = 0; i < node->val.nargs; i++)
+ gin_jsonpath_emit_entries(node->args[i], entries);
+
+ break;
+ }
+ }
+}
+
+static Datum *
+gin_extract_jsonpath_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ JsonPathExtractionContext cxt;
+ JsonPathItem root;
+ JsonPathNode *node;
+ ExtractedJsonPath path = { 0 };
+ GinEntries entries = { 0 };
+
+ gin_jsonpath_init_context(&cxt, pathOps, (jp->header & JSONPATH_LAX) != 0);
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? gin_extract_jsonpath_node(&cxt, &root, path, NULL)
+ : gin_extract_jsonpath_expr(&cxt, &root, path, false);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ gin_jsonpath_emit_entries(node, &entries);
+
+ *nentries = entries.count;
+ if (!*nentries)
+ return NULL;
+
+ *extra_data = palloc0(sizeof(**extra_data) * entries.count);
+ **extra_data = (Pointer) node;
+
+ return entries.buf;
+}
+
+static GinTernaryValue
+gin_execute_jsonpath(JsonPathNode *node, void *check, bool ternary)
+{
+ GinTernaryValue res;
+ GinTernaryValue v;
+ int i;
+
+ switch (node->type)
+ {
+ case eAnd:
+ res = GIN_TRUE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = gin_execute_jsonpath(node->args[i], check, ternary);
+ if (v == GIN_FALSE)
+ return GIN_FALSE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eOr:
+ res = GIN_FALSE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = gin_execute_jsonpath(node->args[i], check, ternary);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case eEntry:
+ {
+ int index = node->val.entryIndex;
+ bool maybe = ternary
+ ? ((GinTernaryValue *) check)[index] != GIN_FALSE
+ : ((bool *) check)[index];
+
+ return maybe ? GIN_MAYBE : GIN_FALSE;
+ }
+
+ default:
+ elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+ return GIN_FALSE;
+ }
}
Datum
@@ -181,6 +744,18 @@ gin_extract_jsonb_query(PG_FUNCTION_ARGS)
if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
*searchMode = GIN_SEARCH_MODE_ALL;
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(0);
+ Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
+
+ entries = gin_extract_jsonpath_query(jp, strategy, false, nentries,
+ extra_data);
+
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
else
{
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -199,7 +774,7 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3);
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
bool *recheck = (bool *) PG_GETARG_POINTER(5);
bool res = true;
int32 i;
@@ -256,6 +831,15 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ JsonPathNode *node = (JsonPathNode *) extra_data[0];
+
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath(node, check, false) != GIN_FALSE;
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +854,7 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3);
-
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
GinTernaryValue res = GIN_MAYBE;
int32 i;
@@ -308,6 +891,12 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ res = nkeys <= 0 ? GIN_MAYBE :
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check, true);
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -331,14 +920,13 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
- int total = 2 * JB_ROOT_COUNT(jb);
+ int total = JB_ROOT_COUNT(jb);
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken r;
PathHashStack tail;
PathHashStack *stack;
- int i = 0;
- Datum *entries;
+ GinEntries entries;
/* If the root level is empty, we certainly have no keys */
if (total == 0)
@@ -348,7 +936,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
}
/* Otherwise, use 2 * root count as initial estimate of result size */
- entries = (Datum *) palloc(sizeof(Datum) * total);
+ gin_entries_init(&entries, 2 * total);
/* We keep a stack of partial hashes corresponding to parent key levels */
tail.parent = NULL;
@@ -361,13 +949,6 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
{
PathHashStack *parent;
- /* Since we recurse into the object, we might need more space */
- if (i >= total)
- {
- total *= 2;
- entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
- }
-
switch (r)
{
case WJB_BEGIN_ARRAY:
@@ -398,7 +979,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
/* mix the element or value's hash into the prepared hash */
JsonbHashScalarValue(&v, &stack->hash);
/* and emit an index entry */
- entries[i++] = UInt32GetDatum(stack->hash);
+ gin_entries_add(&entries, UInt32GetDatum(stack->hash));
/* reset hash for next key, value, or sub-object */
stack->hash = stack->parent->hash;
break;
@@ -419,9 +1000,9 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
}
}
- *nentries = i;
+ *nentries = entries.count;
- PG_RETURN_POINTER(entries);
+ PG_RETURN_POINTER(entries.buf);
}
Datum
@@ -432,18 +1013,35 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
Datum *entries;
- if (strategy != JsonbContainsStrategyNumber)
- elog(ERROR, "unrecognized strategy number: %d", strategy);
+ if (strategy == JsonbContainsStrategyNumber)
+ {
+ /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
+ entries = (Datum *)
+ DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
+ PG_GETARG_DATUM(0),
+ PointerGetDatum(nentries)));
- /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
- entries = (Datum *)
- DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
- PG_GETARG_DATUM(0),
- PointerGetDatum(nentries)));
+ /* ... although "contains {}" requires a full index scan */
+ if (*nentries == 0)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(0);
+ Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
- /* ... although "contains {}" requires a full index scan */
- if (*nentries == 0)
- *searchMode = GIN_SEARCH_MODE_ALL;
+ entries = gin_extract_jsonpath_query(jp, strategy, true, nentries,
+ extra_data);
+
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+ entries = NULL;
+ }
PG_RETURN_POINTER(entries);
}
@@ -456,32 +1054,42 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3);
-
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
bool *recheck = (bool *) PG_GETARG_POINTER(5);
bool res = true;
int32 i;
- if (strategy != JsonbContainsStrategyNumber)
- elog(ERROR, "unrecognized strategy number: %d", strategy);
-
- /*
- * jsonb_path_ops is necessarily lossy, not only because of hash
- * collisions but also because it doesn't preserve complete information
- * about the structure of the JSON object. Besides, there are some
- * special rules around the containment of raw scalars in arrays that are
- * not handled here. So we must always recheck a match. However, if not
- * all of the keys are present, the tuple certainly doesn't match.
- */
- *recheck = true;
- for (i = 0; i < nkeys; i++)
+ if (strategy == JsonbContainsStrategyNumber)
{
- if (!check[i])
+ /*
+ * jsonb_path_ops is necessarily lossy, not only because of hash
+ * collisions but also because it doesn't preserve complete information
+ * about the structure of the JSON object. Besides, there are some
+ * special rules around the containment of raw scalars in arrays that are
+ * not handled here. So we must always recheck a match. However, if not
+ * all of the keys are present, the tuple certainly doesn't match.
+ */
+ *recheck = true;
+ for (i = 0; i < nkeys; i++)
{
- res = false;
- break;
+ if (!check[i])
+ {
+ res = false;
+ break;
+ }
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ JsonPathNode *node = (JsonPathNode *) extra_data[0];
+
+ *recheck = true;
+ res = nkeys <= 0 ||
+ gin_execute_jsonpath(node, check, false) != GIN_FALSE;
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1102,34 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3);
-
- /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+ Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
GinTernaryValue res = GIN_MAYBE;
int32 i;
- if (strategy != JsonbContainsStrategyNumber)
- elog(ERROR, "unrecognized strategy number: %d", strategy);
-
- /*
- * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
- * corresponds to always forcing recheck in the regular consistent
- * function, for the reasons listed there.
- */
- for (i = 0; i < nkeys; i++)
+ if (strategy == JsonbContainsStrategyNumber)
{
- if (check[i] == GIN_FALSE)
+ /*
+ * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
+ * corresponds to always forcing recheck in the regular consistent
+ * function, for the reasons listed there.
+ */
+ for (i = 0; i < nkeys; i++)
{
- res = GIN_FALSE;
- break;
+ if (check[i] == GIN_FALSE)
+ {
+ res = GIN_FALSE;
+ break;
+ }
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ res = nkeys <= 0 ? GIN_MAYBE :
+ gin_execute_jsonpath((JsonPathNode *) extra_data[0], check, true);
+ }
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_GIN_TERNARY_VALUE(res);
}
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index fb58f774b9..c5771e26dc 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1489,11 +1489,23 @@
{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)',
amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '15',
+ amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '16',
+ amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' },
# GIN jsonb_path_ops
{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)',
amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '15',
+ amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '16',
+ amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' },
# SP-GiST range_ops
{ amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange',
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index e0199f5f93..2ea1ec1ac8 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 664c55d986..03158e7986 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 3c6d853ffb..99f46dc884 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1860,6 +1860,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1924,7 +1926,7 @@ ORDER BY 1, 2, 3;
4000 | 26 | >>
4000 | 27 | >>=
4000 | 28 | ^@
-(122 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 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;
--
2.15.2 (Apple Git-101.1)
=
jsonpath_check.sql (application/octet-stream, 13.3K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-09-07 23:21 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Stas Kelvich <s.kelvich@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-09-07 23:21 UTC (permalink / raw)
To: Stas Kelvich <s.kelvich@postgrespro.ru>; +Cc: pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 18th version of the patches rebased onto the current master.
On 04.09.2018 17:15, Stas Kelvich wrote:
>> On 23 Aug 2018, at 00:29, Nikita Glukhov<n.gluhov@postgrespro.ru> wrote:
>>
>> Attached 17th version of the patches rebased onto the current master.
>>
>> Nothing significant has changed.
> Hey.
>
> I’ve looked through presented implementation of jsonpath and have some remarks:
Thank you for your review.
> 1. Current patch set adds functions named jsonpath_* that implementing subset of functionality
> of corresponding json_* functions which doesn’t require changes to postgres SQL grammar.
> If we are going to commit json_* functions it will be quite strange for end user: two sets of functions
> with mostly identical behaviour, except that one of them allows customise error handling and pass variable via
> special language constructions. I suggest only leave operators in this patch, but remove functions.
We can't simply remove these functions because they are the implementation
of operators.
> 2. Handling of Unknown value differs from one that described in tech report: filter '(@.k == 1)’
> doesn’t evaluates to Unknown. Consider following example:
>
> select '42'::json @* '$ ? ( (@.k == 1) is unknown )';
>
> As per my understanding of standard this should show 42. Seems that it was evaluated to False instead,
> because boolean operators (! || &&) on filter expression with structural error behave like it is False.
Yes, this example should return 42, but only in the strict mode. In the lax
mode, which is default, structural errors are ignored, so '@.k' evaluates to
an empty sequence, and '@.k == 1' then returns False.
> 3. .keyvalue() returns object with field named ‘key’ instead of ’name’
> as per tech report. ‘key’ field seems to be more consistent with function
> name, but i’m not sure it is worths of mismatch with standard. Also ‘id’
> field is omitted, making it harder to use something like GROUP BY afterwards.
SQL/JSON standard uses "key" as a field name (9.39, page 716):
vi) For all h, 1 <= h <= q, let OBJh be an SQL/JSON object with three members:
1) The first member has key "key" and bound value Ki .
2) The second member has key "value”"and bound value BVi.
3) The third member has key "id" and bound value IDj.
But "id" field was really missing in our implementation. I have added it
using byte offset of the object in jsonb as its identifier.
> 4. Looks like jsonpath executor lacks some CHECK_FOR_INTERRUPTS() during hot paths. Backend with
> following query is unresponsive to signals:
>
> select count(*) from (
> select '[0,1,2,3,4,5,6,7,8,9]'::json @* longpath::jsonpath from (
> select '$[' || string_agg(subscripts, ',') ||']' as longpath from (
> select 'last,1' as subscripts from generate_series(1,1000000)
> ) subscripts_q
> ) jpath_q
> ) count_q;
Fixed: added CHECK_FOR_INTERRUPTS() to jsonpath parser and executor.
> 5. Files generated by lex/bison should be listed in .gitignore files in corresponding directories.
Fixed.
> 6. My compiler complains about unused functions: JsonValueListConcat, JsonValueListClear.
Fixed: these functions used only in the next SQL/JSON patches were removed.
> 7. Presented patch files structure is somewhat complicated with patches to patches. I've melded them
> down to following patches:
>
> 0001: three first patches with preliminary datetime infrastructure
> 0002: Jsonpath engine and operators that is your previous 4+6+7
> 0003: Jsonpath extensions is your previous 8+9
> 0004: GIN support is your 5th path
>
> Also this patches were formed with 'git format-patch', so one can apply all of them with 'git apply'
> restoring each one as commit.
New patch set is formed with 'git format-patch', but I can still supply patches
in the previous split form.
> 8. Also sending jsonpath_check.sql with queries which I used to check compliance with the standard.
> That can be added to regression test, if you want.
I think these additional tests can be added, but I have not yet done it in this
version of the patches.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (4 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v18.patch (text/x-patch, 15.3K) download
From 1cfe168162556f4b8d48a2baaf2f1aa6573920b5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 8 Sep 2018 01:27:22 +0300
Subject: [PATCH 1/4] Preliminary datetime infrastructure
---
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 314 +++++++++++++++++++++++++++++++++++--
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, 311 insertions(+), 25 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 87146a2..bf75332 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -41,11 +41,6 @@
#endif
-static int tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result);
-static int tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result);
-static void AdjustTimeForTypmod(TimeADT *time, int32 typmod);
-
-
/* common code for timetypmodin and timetztypmodin */
static int32
anytime_typmodin(bool istz, ArrayType *ta)
@@ -1260,7 +1255,7 @@ time_in(PG_FUNCTION_ARGS)
/* tm2time()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2time(struct pg_tm *tm, fsec_t fsec, TimeADT *result)
{
*result = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec)
@@ -1426,7 +1421,7 @@ time_scale(PG_FUNCTION_ARGS)
* have a fundamental tie together but rather a coincidence of
* implementation. - thomas
*/
-static void
+void
AdjustTimeForTypmod(TimeADT *time, int32 typmod)
{
static const int64 TimeScales[MAX_TIME_PRECISION + 1] = {
@@ -2004,7 +1999,7 @@ time_part(PG_FUNCTION_ARGS)
/* tm2timetz()
* Convert a tm structure to a time data type.
*/
-static int
+int
tm2timetz(struct pg_tm *tm, fsec_t fsec, int tz, TimeTzADT *result)
{
result->time = ((((tm->tm_hour * MINS_PER_HOUR + tm->tm_min) * SECS_PER_MINUTE) + tm->tm_sec) *
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 30696e3..9df30f4 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -87,6 +87,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -947,6 +948,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
@@ -961,7 +966,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);
@@ -978,8 +984,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,
- 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, 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);
@@ -2974,13 +2980,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;
@@ -3262,6 +3270,120 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
break;
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s == ' ')
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
+}
+
+/* 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 */
@@ -3563,7 +3685,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3598,7 +3721,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3620,6 +3744,155 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
@@ -3631,14 +3904,20 @@ 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len;
+ char *fmt_tmp = NULL;
char *date_str;
int fmask;
@@ -3649,15 +3928,15 @@ do_to_timestamp(text *date_txt, text *fmt,
*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)
{
/*
@@ -3687,13 +3966,18 @@ 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);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
- pfree(fmt_str);
if (!incache)
pfree(format);
}
+ if (fmt_tmp)
+ pfree(fmt_tmp);
+
DEBUG_TMFC(&tmfc);
/*
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 284e14d..5bec100 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 eb6d2a1..10cc822 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 d66582b..d3dd851 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 a9f5548..208cc00 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
--
2.7.4
0002-Jsonpath-engine-and-operators-v18.patch (text/x-patch, 343.5K) download
From 956787e67db971dc01d14aefdd29dd7adfecbc2a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 8 Sep 2018 01:27:22 +0300
Subject: [PATCH 2/4] Jsonpath engine and operators
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 25 +-
src/backend/utils/adt/float.c | 48 +-
src/backend/utils/adt/formatting.c | 43 +-
src/backend/utils/adt/json.c | 865 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 871 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2813 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 495 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 623 ++++++
src/backend/utils/adt/numeric.c | 294 ++-
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 10 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/elog.h | 19 +
src/include/utils/float.h | 7 +-
src/include/utils/formatting.h | 4 +-
src/include/utils/jsonapi.h | 63 +-
src/include/utils/jsonb.h | 34 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 118 ++
src/include/utils/jsonpath_scanner.h | 30 +
src/include/utils/numeric.h | 9 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 800 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
41 files changed, 11886 insertions(+), 188 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_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
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 3a58bf6..92c881a 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 798a823..da0c098 100644
--- a/src/backend/lib/stringinfo.c
+++ b/src/backend/lib/stringinfo.c
@@ -306,3 +306,24 @@ enlargeStringInfo(StringInfo str, int needed)
str->maxlen = newlen;
}
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+void
+alignStringInfoInt(StringInfo buf)
+{
+ switch(INTALIGN(buf->len) - buf->len)
+ {
+ case 3:
+ appendStringInfoCharMacro(buf, 0);
+ case 2:
+ appendStringInfoCharMacro(buf, 0);
+ case 1:
+ appendStringInfoCharMacro(buf, 0);
+ default:
+ break;
+ }
+}
+
diff --git a/src/backend/utils/adt/.gitignore b/src/backend/utils/adt/.gitignore
new file mode 100644
index 0000000..7fab054
--- /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 4b35dbb..e2ad685 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -17,7 +17,8 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
float.o format_type.o formatting.o genfile.o \
geo_ops.o geo_selfuncs.o geo_spgist.o inet_cidr_ntop.o inet_net_pton.o \
int.o int8.o json.o jsonb.o jsonb_gin.o jsonb_op.o jsonb_util.o \
- jsonfuncs.o like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
+ jsonfuncs.o jsonpath_gram.o jsonpath_scan.o jsonpath.o jsonpath_exec.o jsonpath_json.o \
+ like.o lockfuncs.o mac.o mac8.o misc.o nabstime.o name.o \
network.o network_gist.o network_selfuncs.o network_spgist.o \
numeric.o numutils.o oid.o oracle_compat.o \
orderedsetaggs.o pg_locale.o pg_lsn.o pg_upgrade_support.o \
@@ -32,6 +33,28 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
txid.o uuid.o varbit.o varchar.o varlena.o version.o \
windowfuncs.o xid.o xml.o
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
+
+jsonpath_gram.c: BISONFLAGS += -d
+
+jsonpath_scan.c: FLEXFLAGS = -CF -p -p
+
+jsonpath_gram.h: jsonpath_gram.c ;
+
+# Force these dependencies to be known even without dependency info built:
+jsonpath_gram.o jsonpath_scan.o jsonpath_parser.o: jsonpath_gram.h
+
+jsonpath_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index df35557..0628ede 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -307,7 +307,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
@@ -325,8 +325,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;
@@ -340,10 +340,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);
@@ -416,17 +419,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
@@ -449,10 +456,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/formatting.c b/src/backend/utils/adt/formatting.c
index 9df30f4..be139a5 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3749,8 +3749,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;
@@ -3759,6 +3759,7 @@ to_datetime(text *date_txt, const char *fmt, int fmt_len, bool strict,
do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &flags);
*typmod = -1; /* TODO implement FF1, ..., FF9 */
+ *tz = 0;
if (flags & DCH_DATED)
{
@@ -3767,20 +3768,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")));
@@ -3844,20 +3852,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 6f0fe94..b19d7b1 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;
}
@@ -1506,7 +1511,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 +1519,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 +1527,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 +1558,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 +1635,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),
@@ -2553,3 +2577,804 @@ 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,
+ &jbv->val.datetime.tz);
+
+ 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.c b/src/backend/utils/adt/jsonb.c
index 0ae9d7b..00a7f3a 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 3be900f..fd2da1f 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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);
@@ -822,6 +822,7 @@ recurse:
/* 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
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,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.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..11d457d
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..e017ac1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2813 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#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
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Numeric (*func)(Numeric, Numeric, ErrorData **);
+ Numeric res;
+ bool hasNext;
+ ErrorData *edata;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add_internal;
+ break;
+ case jpiSub:
+ func = numeric_sub_internal;
+ break;
+ case jpiMul:
+ func = numeric_mul_internal;
+ break;
+ case jpiDiv:
+ func = numeric_div_internal;
+ break;
+ case jpiMod:
+ func = numeric_mod_internal;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ edata = NULL;
+ res = func(lval->val.numeric, rval->val.numeric, &edata);
+
+ if (edata)
+ return jperMakeErrorData(edata);
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = res;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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 *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, fmt, fmtlen, tzname, strict,
+ typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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_t 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = singleton ? jb : binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ ErrorData *edata = NULL;
+
+ if (JsonbType(jb) == jbvScalar)
+ jb = JsonbExtractScalar(jb->val.binary.data, &jbv);
+
+ if (jb->type == jbvNumeric)
+ {
+ /* 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 = float8_numeric_internal(val, &edata);
+ }
+ }
+ else
+ {
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ break;
+ }
+
+ if (edata)
+ {
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ThrowErrorData(edata);
+
+ FreeErrorData(edata);
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ else
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ if (tryToParseDatetime(template_str, template_len, datetime,
+ tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ {
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue idval;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbValue idstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+ int64_t id;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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 :
+#ifdef JSONPATH_JSON_C
+ (int64_t)((char *)((JsonContainer *) jb->val.binary.data)->data -
+ (char *) cxt->baseObject.jbc->data);
+#else
+ (int64_t)((char *) jb->val.binary.data -
+ (char *) cxt->baseObject.jbc);
+#endif
+ id += (int64_t) 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ 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 inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..3856a06
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..8101ffb
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,623 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 444e575..8893878 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index d8b6921..b4b8767 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 29efb3f..444b2d5 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index d9b6bad..91b0389 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3336,5 +3336,33 @@
{ 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' },
+{ 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 8605714..50703ad 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9296,6 +9296,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 48e01cd..8f2fc29 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -770,6 +770,16 @@
typelem => 'jsonb', typinput => 'array_in', typoutput => 'array_out',
typreceive => 'array_recv', typsend => 'array_send',
typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' },
+{ oid => '6050',
+ typname => 'jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'U',
+ typarray => '_jsonpath', typinput => 'jsonpath_in',
+ typoutput => 'jsonpath_out', typreceive => '-', typsend => '-',
+ typalign => 'i', typstorage => 'x' }
+{ oid => '6051',
+ typname => '_jsonpath', typlen => '-1', typbyval => 'f', typcategory => 'A',
+ typelem => 'jsonpath', typinput => 'array_in', typoutput => 'array_out',
+ typreceive => 'array_recv', typsend => 'array_send',
+ typanalyze => 'array_typanalyze', typalign => 'i', typstorage => 'x' },
{ oid => '2970', descr => 'txid snapshot',
typname => 'txid_snapshot', typlen => '-1', typbyval => 'f',
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/elog.h b/src/include/utils/elog.h
index 33c6b53..42a834c 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 05e1b27..d082bdc 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/formatting.h b/src/include/utils/formatting.h
index 208cc00..6db5b3f 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 6b483a1..6ef601f 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
@@ -161,6 +204,24 @@ 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);
+
+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 27873d4..e0199f5 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;
@@ -219,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
@@ -236,7 +238,9 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+ /* Virtual types */
+ jbvDatetime = 0x20,
};
/*
@@ -269,6 +273,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 */
@@ -277,11 +283,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,6 +370,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,
@@ -363,6 +380,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);
@@ -379,5 +398,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 0000000..73e3f31
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ uint32 header; /* just version, other bits are reservedfor future use */
+ 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)))
+#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,
+ 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,
+} 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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..a751540
--- /dev/null
+++ b/src/include/utils/jsonpath_json.h
@@ -0,0 +1,118 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_json.h
+ * Jsonpath support for json datatype
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/utils/jsonpath_json.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef JSONPATH_JSON_H
+#define JSONPATH_JSON_H
+
+/* redefine jsonb structures */
+#define Jsonb Json
+#define JsonbContainer JsonContainer
+#define JsonbIterator JsonIterator
+
+/* redefine jsonb functions */
+#define findJsonbValueFromContainer(jc, flags, jbv) \
+ findJsonValueFromContainer((JsonContainer *)(jc), flags, jbv)
+#define getIthJsonbValueFromContainer(jc, i) \
+ getIthJsonValueFromContainer((JsonContainer *)(jc), i)
+#define pushJsonbValue pushJsonValue
+#define JsonbIteratorInit(jc) JsonIteratorInit((JsonContainer *)(jc))
+#define JsonbIteratorNext JsonIteratorNext
+#define JsonbValueToJsonb JsonbValueToJson
+#define JsonbToCString JsonToCString
+#define JsonbUnquote JsonUnquote
+#define JsonbExtractScalar(jc, jbv) JsonExtractScalar((JsonContainer *)(jc), jbv)
+
+/* redefine jsonb macros */
+#undef JsonContainerSize
+#define JsonContainerSize(jc) \
+ ((((JsonContainer *)(jc))->header & JB_CMASK) == JB_CMASK && \
+ JsonContainerIsArray(jc) \
+ ? JsonGetArraySize((JsonContainer *)(jc)) \
+ : ((JsonContainer *)(jc))->header & JB_CMASK)
+
+
+#undef DatumGetJsonbP
+#define DatumGetJsonbP(d) DatumGetJsonP(d)
+
+#undef DatumGetJsonbPCopy
+#define DatumGetJsonbPCopy(d) DatumGetJsonPCopy(d)
+
+#undef JsonbPGetDatum
+#define JsonbPGetDatum(json) JsonPGetDatum(json)
+
+#undef PG_GETARG_JSONB_P
+#define PG_GETARG_JSONB_P(n) DatumGetJsonP(PG_GETARG_DATUM(n))
+
+#undef PG_GETARG_JSONB_P_COPY
+#define PG_GETARG_JSONB_P_COPY(n) DatumGetJsonPCopy(PG_GETARG_DATUM(n))
+
+#undef PG_RETURN_JSONB_P
+#define PG_RETURN_JSONB_P(json) PG_RETURN_DATUM(JsonPGetDatum(json))
+
+
+#ifdef DatumGetJsonb
+#undef DatumGetJsonb
+#define DatumGetJsonb(d) DatumGetJsonbP(d)
+#endif
+
+#ifdef DatumGetJsonbCopy
+#undef DatumGetJsonbCopy
+#define DatumGetJsonbCopy(d) DatumGetJsonbPCopy(d)
+#endif
+
+#ifdef JsonbGetDatum
+#undef JsonbGetDatum
+#define JsonbGetDatum(json) JsonbPGetDatum(json)
+#endif
+
+#ifdef PG_GETARG_JSONB
+#undef PG_GETARG_JSONB
+#define PG_GETARG_JSONB(n) PG_GETARG_JSONB_P(n)
+#endif
+
+#ifdef PG_GETARG_JSONB_COPY
+#undef PG_GETARG_JSONB_COPY
+#define PG_GETARG_JSONB_COPY(n) PG_GETARG_JSONB_P_COPY(n)
+#endif
+
+#ifdef PG_RETURN_JSONB
+#undef PG_RETURN_JSONB
+#define PG_RETURN_JSONB(json) PG_RETURN_JSONB_P(json)
+#endif
+
+/* redefine global jsonpath functions */
+#define executeJsonPath executeJsonPathJson
+#define JsonbPathExists JsonPathExists
+#define JsonbPathQuery JsonPathQuery
+#define JsonbPathValue JsonPathValue
+#define JsonbTableRoutine JsonTableRoutine
+
+#define JsonbWrapItemInArray JsonWrapItemInArray
+#define JsonbWrapItemsInArray JsonWrapItemsInArray
+#define JsonbArraySize JsonArraySize
+#define JsonValueListConcat JsonValueListConcatJson
+#define jspRecursiveExecute jspRecursiveExecuteJson
+#define jspRecursiveExecuteNested jspRecursiveExecuteNestedJson
+#define jspCompareItems jspCompareItemsJson
+
+static inline JsonbValue *
+JsonbInitBinary(JsonbValue *jbv, Json *jb)
+{
+ jbv->type = jbvBinary;
+ jbv->val.binary.data = (void *) &jb->root;
+ jbv->val.binary.len = jb->root.len;
+
+ return jbv;
+}
+
+#endif /* JSONPATH_JSON_H */
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index cd8da8b..6e3e3f0 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_ */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..1c71984
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"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")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..66dea4b
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?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?
+-----------------------------------------------
+ {"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?
+-----------------------------------------------
+ {"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()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..193fc68
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 16f979c..2e0cd2d 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be..494cceb 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -160,6 +160,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000..824f510
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..43f34ef
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..8a3ea42
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 14d2a3c..90db7e5 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -174,6 +174,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 68cf812..9998e16 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',
--
2.7.4
0003-Jsonpath-syntax-extensions-v18.patch (text/x-patch, 46.2K) download
From 72dcf4b884d7f523d611375a71f0c664ca277c29 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 8 Sep 2018 01:27:23 +0300
Subject: [PATCH 3/4] Jsonpath syntax extensions
---
src/backend/utils/adt/jsonpath.c | 153 ++++++++++-
src/backend/utils/adt/jsonpath_exec.c | 394 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 +++++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 +++++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 +++++
src/test/regress/sql/json_jsonpath.sql | 47 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1227 insertions(+), 78 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11d457d..456db2e 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -136,12 +136,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
nestingLevel + argNestingLevel,
insideArraySubscript);
@@ -218,6 +221,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -305,6 +363,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -494,12 +554,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -563,6 +623,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -585,7 +693,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -688,6 +796,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -699,6 +808,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -713,7 +832,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -765,7 +885,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -869,3 +992,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index e017ac1..853c899 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -83,6 +83,8 @@ static inline JsonPathExecResult recursiveExecuteNested(JsonPathExecContext *cxt
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1686,7 +1688,116 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{
int innermostArraySize = cxt->innermostArraySize;
int i;
@@ -1771,9 +1882,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -2051,7 +2166,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2062,84 +2176,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- if (tryToParseDatetime(template_str, template_len, datetime,
- tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tzname)
- pfree(tzname);
- }
- else
- {
- const char *templates[] = {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- int i;
-
- for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ if (tryToParseDatetime(template_str, template_len,
+ datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (tryToParseDatetime(templates[i], -1, datetime,
- NULL, true, &value, &typid,
- &typmod, &tz))
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
{
- res = jperOk;
- break;
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2269,6 +2412,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 3856a06..be1d488 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -255,6 +255,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -288,9 +308,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -314,7 +334,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -327,6 +347,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -381,6 +415,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 73e3f31..664c55d 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -82,6 +82,10 @@ typedef enum JsonPathItemType {
jpiLast,
jpiStartsWith,
jpiLikeRegex,
+ jpiSequence,
+ jpiArray,
+ jpiObject,
+ jpiObjectField,
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -136,6 +140,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -162,6 +179,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -207,6 +227,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 1c71984..86e553d 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 66dea4b..1d3215b 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 193fc68..ea29105 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 824f510..0901876 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 43f34ef..ad7a320 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 8a3ea42..653f928 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.7.4
0004-Jsonpath-GIN-support-v18.patch (text/x-patch, 42.8K) download
From 02029240396e8f719cbb03f793f229d1e36e2617 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 8 Sep 2018 01:27:22 +0300
Subject: [PATCH 4/4] Jsonpath GIN support
---
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 c8a2745..c11960c 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 fb58f77..c5771e2 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1489,11 +1489,23 @@
{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)',
amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '15',
+ amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '16',
+ amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' },
# GIN jsonb_path_ops
{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)',
amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '15',
+ amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+ amoprighttype => 'jsonpath', amopstrategy => '16',
+ amopopr => '@~(jsonb,jsonpath)', amopmethod => 'gin' },
# SP-GiST range_ops
{ amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange',
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index e0199f5..2ea1ec1 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 664c55d..03158e7 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 f045e08..92cf4c7 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 3c6d853..99f46dc 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1860,6 +1860,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1924,7 +1926,7 @@ ORDER BY 1, 2, 3;
4000 | 26 | >>
4000 | 27 | >>=
4000 | 28 | ^@
-(122 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index bd82fd1..1430a98 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;
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-10-02 02:33 Michael Paquier <michael@paquier.xyz>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Michael Paquier @ 2018-10-02 02:33 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sat, Sep 08, 2018 at 02:21:27AM +0300, Nikita Glukhov wrote:
> Attached 18th version of the patches rebased onto the current master.
Nikita, this version fails to apply, as 0004 has conflicts with some
regression tests. Could you rebase? I am moving the patch to CF
2018-11, waiting for your input.
--
Michael
Attachments:
signature.asc (application/pgp-signature, 832B) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-10-28 23:20 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Michael Paquier <michael@paquier.xyz>
0 siblings, 2 replies; 198+ messages in thread
From: Tomas Vondra @ 2018-10-28 23:20 UTC (permalink / raw)
To: Michael Paquier <michael@paquier.xyz>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 10/02/2018 04:33 AM, Michael Paquier wrote:
> On Sat, Sep 08, 2018 at 02:21:27AM +0300, Nikita Glukhov wrote:
>> Attached 18th version of the patches rebased onto the current master.
>
> Nikita, this version fails to apply, as 0004 has conflicts with some
> regression tests. Could you rebase? I am moving the patch to CF
> 2018-11, waiting for your input.
> --
> Michael
>
As Michael mentioned, the patch does not apply anymore, so it would be
good to provide a rebased version for CF 2018-11. I've managed to do
that, as the issues are due to minor bitrot, so that I can do some quick
review of the current version.
I haven't done much testing, pretty much just compiling, running the
usual regression tests and reading through the patches. I plan to do
more testing once the rebased patch is submitted.
Now, a couple of comments based on eye-balling the diffs.
1) There are no docs, which makes it pretty much non-committable for
now. I know there is [1] and it was a good intro for the review, but we
need something as a part of our sgml docs.
2) 0001 says this:
*typmod = -1; /* TODO implement FF1, ..., FF9 */
but I have no idea what FF1 or FF9 are. I guess it needs to be
implemented, or explained better.
3) The makefile rule for scan.o does this:
+# Latest flex causes warnings in this file.
+ifeq ($(GCC),yes)
+scan.o: CFLAGS += -Wno-error
+endif
That seems a bit ugly, and we should probably try to make it work with
the latest flex, instead of hiding the warnings. I don't think we have
any such ad-hoc rules in other Makefiles. If we really need it, can't we
make it part of configure, and/or restrict it depending on flex version?
4) There probably should be .gitignore rule for jsonpath_gram.h, just
like for other generated header files.
5) jbvType says jbvDatetime is "virtual type" but does not explain what
it is. IMHO that deserves a comment or something.
6) I see the JsonPath definition says this about header:
/* just version, other bits are reservedfor future use */
but the very next thing it does is defining two pieces stored in the
header - version AND "lax" mode flag. Which makes the comment invalid
(also, note the missing space after "reserved").
FWIW, I'd use JSONPATH_STRICT instead of JSONPATH_LAX. The rest of the
codebase works with "strict" flags passed around, and it's easy to
forget to negate the flag somewhere (at least that's my experience).
7) I see src/include/utils/jsonpath_json.h adds support for plain json
by undefining various jsonb macros and redirecting them to the json
variants. I find that rather suspicious - imagine you're investigating
something in code using those jsonb macros, and wondering why it ends up
calling the json stuff. I'd be pretty confused ...
Also, some of the redefinitions are not really needed for example
JsonbWrapItemInArray and JsonbWrapItemsInArray are not used anywhere
(and neither are the json variants).
8) I see 0002 defines IsAJsonbScalar like this:
#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
(jsonbval)->type <= jbvBool) || \
(jsonbval)->type == jbvDatetime)
while 0004 does this
#define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool)
I know those are for different data types (jsonb vs. jsonpath), but I
suppose jspIsScalar should include the datetime too.
FWIW JsonPathItemType would deserve better documentation what the
various items mean (a comment for each line would be enough). I've been
wondering if "jpiDouble" means scalar double value until I realized
there's a ".double()" function for paths.
9) It's generally a good idea to make the individual pieces committable
separately, but that means e.g. the regression tests have to pass after
each patch. At the moment that does not seem to be the case for 0002,
see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
not sure if that's related.
regards
[1] https://github.com/obartunov/sqljsondoc/blob/master/README.jsonpath.md
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
regression.diffs.0002 (text/plain, 1012B) download
*** /home/user/work/postgres/src/test/regress/expected/json_jsonpath.out 2018-10-28 22:20:53.788520950 +0100
--- /home/user/work/postgres/src/test/regress/results/json_jsonpath.out 2018-10-28 22:22:37.786520950 +0100
***************
*** 281,291 ****
(1 row)
select json '{}' @* 'lax $[0]';
! ?column?
! ----------
! {}
! (1 row)
!
select json '[1]' @* 'lax $[0]';
?column?
----------
--- 281,287 ----
(1 row)
select json '{}' @* 'lax $[0]';
! ERROR: unknown jsonb value type: 21030544
select json '[1]' @* 'lax $[0]';
?column?
----------
***************
*** 320,330 ****
(1 row)
select json '{}' @* 'lax $[last]';
! ?column?
! ----------
! {}
! (1 row)
!
select json '[1,2,3]' @* '$[last]';
?column?
----------
--- 316,322 ----
(1 row)
select json '{}' @* 'lax $[last]';
! ERROR: unknown jsonb value type: 21063360
select json '[1,2,3]' @* '$[last]';
?column?
----------
======================================================================
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-10-31 07:29 Oleg Bartunov <obartunov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Oleg Bartunov @ 2018-10-31 07:29 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Michael Paquier <michael@paquier.xyz>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; tgl Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Pavel Stěhule <pavel.stehule@gmail.com>
On Mon, Oct 29, 2018 at 2:20 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
>
> Hi,
>
> On 10/02/2018 04:33 AM, Michael Paquier wrote:
> > On Sat, Sep 08, 2018 at 02:21:27AM +0300, Nikita Glukhov wrote:
> >> Attached 18th version of the patches rebased onto the current master.
> >
> > Nikita, this version fails to apply, as 0004 has conflicts with some
> > regression tests. Could you rebase? I am moving the patch to CF
> > 2018-11, waiting for your input.
> > --
> > Michael
> >
>
> As Michael mentioned, the patch does not apply anymore, so it would be
> good to provide a rebased version for CF 2018-11. I've managed to do
> that, as the issues are due to minor bitrot, so that I can do some quick
> review of the current version.
>
> I haven't done much testing, pretty much just compiling, running the
> usual regression tests and reading through the patches. I plan to do
> more testing once the rebased patch is submitted.
>
> Now, a couple of comments based on eye-balling the diffs.
>
>
> 1) There are no docs, which makes it pretty much non-committable for
> now. I know there is [1] and it was a good intro for the review, but we
> need something as a part of our sgml docs.
SQL/JSON documentation discussed in separate topic
https://www.postgresql.org/message-id/flat/732208d3-56c3-25a4-8f08-3be1d54ad51b%40postgrespro.ru
>
>
> 2) 0001 says this:
>
> *typmod = -1; /* TODO implement FF1, ..., FF9 */
>
> but I have no idea what FF1 or FF9 are. I guess it needs to be
> implemented, or explained better.
>
>
> 3) The makefile rule for scan.o does this:
>
> +# Latest flex causes warnings in this file.
> +ifeq ($(GCC),yes)
> +scan.o: CFLAGS += -Wno-error
> +endif
>
> That seems a bit ugly, and we should probably try to make it work with
> the latest flex, instead of hiding the warnings. I don't think we have
> any such ad-hoc rules in other Makefiles. If we really need it, can't we
> make it part of configure, and/or restrict it depending on flex version?
>
>
> 4) There probably should be .gitignore rule for jsonpath_gram.h, just
> like for other generated header files.
>
>
> 5) jbvType says jbvDatetime is "virtual type" but does not explain what
> it is. IMHO that deserves a comment or something.
>
>
> 6) I see the JsonPath definition says this about header:
>
> /* just version, other bits are reservedfor future use */
>
> but the very next thing it does is defining two pieces stored in the
> header - version AND "lax" mode flag. Which makes the comment invalid
> (also, note the missing space after "reserved").
>
> FWIW, I'd use JSONPATH_STRICT instead of JSONPATH_LAX. The rest of the
> codebase works with "strict" flags passed around, and it's easy to
> forget to negate the flag somewhere (at least that's my experience).
>
>
> 7) I see src/include/utils/jsonpath_json.h adds support for plain json
> by undefining various jsonb macros and redirecting them to the json
> variants. I find that rather suspicious - imagine you're investigating
> something in code using those jsonb macros, and wondering why it ends up
> calling the json stuff. I'd be pretty confused ...
>
> Also, some of the redefinitions are not really needed for example
> JsonbWrapItemInArray and JsonbWrapItemsInArray are not used anywhere
> (and neither are the json variants).
>
>
> 8) I see 0002 defines IsAJsonbScalar like this:
>
> #define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
> (jsonbval)->type <= jbvBool) || \
> (jsonbval)->type == jbvDatetime)
>
> while 0004 does this
>
> #define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool)
>
> I know those are for different data types (jsonb vs. jsonpath), but I
> suppose jspIsScalar should include the datetime too.
>
> FWIW JsonPathItemType would deserve better documentation what the
> various items mean (a comment for each line would be enough). I've been
> wondering if "jpiDouble" means scalar double value until I realized
> there's a ".double()" function for paths.
>
>
> 9) It's generally a good idea to make the individual pieces committable
> separately, but that means e.g. the regression tests have to pass after
> each patch. At the moment that does not seem to be the case for 0002,
> see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
> not sure if that's related.
>
> regards
>
>
> [1] https://github.com/obartunov/sqljsondoc/blob/master/README.jsonpath.md
>
> --
> Tomas Vondra http://www.2ndQuadrant.com
> PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>
--
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-06 14:31 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 2 replies; 198+ messages in thread
From: Nikita Glukhov @ 2018-11-06 14:31 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 29.10.2018 2:20, Tomas Vondra wrote:
> On 10/02/2018 04:33 AM, Michael Paquier wrote:
>> On Sat, Sep 08, 2018 at 02:21:27AM +0300, Nikita Glukhov wrote:
>>> Attached 18th version of the patches rebased onto the current master.
>> Nikita, this version fails to apply, as 0004 has conflicts with some
>> regression tests. Could you rebase? I am moving the patch to CF
>> 2018-11, waiting for your input.
>> --
>> Michael
>>
> As Michael mentioned, the patch does not apply anymore, so it would be
> good to provide a rebased version for CF 2018-11. I've managed to do
> that, as the issues are due to minor bitrot, so that I can do some quick
> review of the current version.
Sorry that I failed to provide rebased version earlier.
Attached 19th version of the patches rebased onto the current master.
> I haven't done much testing, pretty much just compiling, running the
> usual regression tests and reading through the patches. I plan to do
> more testing once the rebased patch is submitted.
>
> Now, a couple of comments based on eye-balling the diffs.
Thank you for your review.
> 1) There are no docs, which makes it pretty much non-committable for
> now. I know there is [1] and it was a good intro for the review, but we
> need something as a part of our sgml docs.
I think that jsonpath part of documentation can be extracted from [2] and
added to the patch set.
> 2) 0001 says this:
>
> *typmod = -1; /* TODO implement FF1, ..., FF9 */
>
> but I have no idea what FF1 or FF9 are. I guess it needs to be
> implemented, or explained better.
FF1-FF9 are standard datetime template fields used for specifying of fractional
seconds. FF3/FF6 are analogues of our MS/US. I decided simply to implement
this feature (see patch 0001, I also can supply it in the separate patch).
But FF7-FF9 are questionable since the maximal supported precision is only 6.
They are optional by the standard:
95) Specifications for Feature F555, “Enhanced seconds precision”:
d) Subclause 9.44, “Datetime templates”:
i) Without Feature F555, “Enhanced seconds precision”,
a <datetime template fraction> shall not be FF7, FF8 or FF9.
So I decided to allow FF7-FF9 only on the output in to_char().
> 3) The makefile rule for scan.o does this:
>
> +# Latest flex causes warnings in this file.
> +ifeq ($(GCC),yes)
> +scan.o: CFLAGS += -Wno-error
> +endif
>
> That seems a bit ugly, and we should probably try to make it work with
> the latest flex, instead of hiding the warnings. I don't think we have
> any such ad-hoc rules in other Makefiles. If we really need it, can't we
> make it part of configure, and/or restrict it depending on flex version?
These lines seem to belong to the earliest versions of our jsonpath
implementation. There is no scan.o file now at all, there is only
jsonpath_scan.o, so I simply removed these lines.
> 4) There probably should be .gitignore rule for jsonpath_gram.h, just
> like for other generated header files.
I see 3 rules in /src/backend/utils/adt/.gitignore:
/jsonpath_gram.h
/jsonpath_gram.c
/jsonpath_scan.c
> 5) jbvType says jbvDatetime is "virtual type" but does not explain what
> it is. IMHO that deserves a comment or something.
"Virtual type" means here that it is used only for in-memory processing and
converted into JSON string when outputted to jsonb. Corresponding comment
was added.
> 6) I see the JsonPath definition says this about header:
>
> /* just version, other bits are reservedfor future use */
>
> but the very next thing it does is defining two pieces stored in the
> header - version AND "lax" mode flag. Which makes the comment invalid
> (also, note the missing space after "reserved").
Fixed.
> FWIW, I'd use JSONPATH_STRICT instead of JSONPATH_LAX. The rest of the
> codebase works with "strict" flags passed around, and it's easy to
> forget to negate the flag somewhere (at least that's my experience).
Jsonpath lax/strict mode flag is used only in executeJsonPath() where it is
saved in "laxMode" field. New "strict" flag passed to datetime functions
is unrelated to jsonpath.
> 7) I see src/include/utils/jsonpath_json.h adds support for plain json
> by undefining various jsonb macros and redirecting them to the json
> variants. I find that rather suspicious - imagine you're investigating
> something in code using those jsonb macros, and wondering why it ends up
> calling the json stuff. I'd be pretty confused ...
I agree, this is rather simple but doubtful solution. That's why json support
was in a separate patch until the 18th version of the patches.
But if we do not want to compile jsonpath.c twice with different definitions,
then we need some kind of run-time wrapping over json strings and jsonb
containers, which seems a bit harder to implement.
To simplify debugging I can also suggest to explicitly preprocess jsonpath.c
into jsonpath_json.c using redefinitions from jsonpath_json.h before its
compilation.
> Also, some of the redefinitions are not really needed for example
> JsonbWrapItemInArray and JsonbWrapItemsInArray are not used anywhere
> (and neither are the json variants).
These definitions will be used in the next patches, so I removed them
for now from this patch.
> 8) I see 0002 defines IsAJsonbScalar like this:
>
> #define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
> (jsonbval)->type <= jbvBool) || \
> (jsonbval)->type == jbvDatetime)
>
> while 0004 does this
>
> #define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool)
>
> I know those are for different data types (jsonb vs. jsonpath), but I
> suppose jspIsScalar should include the datetime too.
jpiDatetime does not mean the same thing as jbvDatetime.
jpiDatetime is a representation of ".datetime()" item method.
jbvDatetime is a representation of datetime SQL/JSON items.
Also datetime SQL/JSON items can be represented in JSON path only by strings
with ".datetime()" method applied, they do not have their own literal:
'"2018-01-10".datetime()'
> FWIW JsonPathItemType would deserve better documentation what the
> various items mean (a comment for each line would be enough). I've been
> wondering if "jpiDouble" means scalar double value until I realized
> there's a ".double()" function for paths.
I added per-item comments.
> 9) It's generally a good idea to make the individual pieces committable
> separately, but that means e.g. the regression tests have to pass after
> each patch. At the moment that does not seem to be the case for 0002,
> see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
> not sure if that's related.
This should definitely be a bug in json support, but I can't reproduce
it simply by defining -DRANDOMIZE_ALLOCATED_MEMORY. Could you provide
a stack trace at least?
[1] https://github.com/obartunov/sqljsondoc/blob/master/README.jsonpath.md
[2] https://github.com/postgrespro/sqljson/tree/sqljson_doc
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (4 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v19.patch (text/x-patch, 32.0K) download
From 55098b8aa492223d1f67bac64fc4386ad2aa501a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 1/4] Preliminary datetime infrastructure
---
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 434 +++++++++++++++++++++++++++---
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 86 ++++++
src/test/regress/expected/timestamp.out | 15 ++
src/test/regress/expected/timestamptz.out | 15 ++
src/test/regress/sql/horology.sql | 14 +
src/test/regress/sql/timestamp.sql | 8 +
src/test/regress/sql/timestamptz.sql | 8 +
12 files changed, 562 insertions(+), 40 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cb6b5e55..7d5c8ac 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 2923afe..7cba489 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,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,
@@ -645,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,
@@ -745,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},
@@ -794,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},
@@ -895,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
@@ -962,6 +1000,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1019,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1037,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,
- 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, 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);
@@ -2514,18 +2557,41 @@ 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;
+ case DCH_FF7:
+ DCH_to_char_fsec("%06d0", in->fsec);
break;
+ case DCH_FF8:
+ DCH_to_char_fsec("%06d00", in->fsec);
+ break;
+ case DCH_FF9: /* nanosecond */
+ DCH_to_char_fsec("%06d000", in->fsec);
+ break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,13 +3073,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;
@@ -3149,8 +3217,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3164,6 +3242,9 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
from_char_parse_int(&out->ssss, &s, n);
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF7:
+ case DCH_FF8:
+ case DCH_FF9:
case DCH_tz:
case DCH_TZ:
case DCH_OF:
@@ -3374,6 +3455,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")));
+ }
}
/*
@@ -3394,6 +3492,112 @@ 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_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;
+ 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)
@@ -3682,8 +3886,10 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3907,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);
}
@@ -3718,7 +3928,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3951,160 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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 fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+
+ 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
*
* 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
@@ -3751,14 +4112,20 @@ 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len;
+ char *fmt_tmp = NULL;
char *date_str;
int fmask;
@@ -3769,15 +4136,15 @@ do_to_timestamp(text *date_txt, text *fmt,
*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)
{
/*
@@ -3807,13 +4174,18 @@ 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);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
- pfree(fmt_str);
if (!incache)
pfree(format);
}
+ if (fmt_tmp)
+ pfree(fmt_tmp);
+
DEBUG_TMFC(&tmfc);
/*
@@ -3991,6 +4363,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 449164a..fcc6d23 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 eb6d2a1..10cc822 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 de9e9ad..165f0e7 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 a9f5548..208cc00 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
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..f103d36 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: formatting field "FF7" is only supported in to_char
+SELECT to_timestamp('123', 'FF8');
+ERROR: formatting field "FF8" is only supported in to_char
+SELECT to_timestamp('123', 'FF9');
+ERROR: formatting field "FF9" is only supported in to_char
--
-- 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 4a2fabd..4742f0b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 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 0000000 00000000 000000000 000 000000
+ | 7 78 780 7800 78000 780000 7800000 78000000 780000000 780 780000
+ | 7 78 789 7890 78901 789010 7890100 78901000 789010000 789 789010
+ | 7 78 789 7890 78901 789012 7890120 78901200 789012000 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2340f30..7d73f49 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1699,6 +1699,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 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 0000000 00000000 000000000 000 000000
+ | 7 78 780 7800 78000 780000 7800000 78000000 780000000 780 780000
+ | 7 78 789 7890 78901 789010 7890100 78901000 789010000 789 789010
+ | 7 78 789 7890 78901 789012 7890120 78901200 789012000 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..ef34323 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 b7957cb..ada7bc1 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index f17d153..e1ef747 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
--
2.7.4
0002-Jsonpath-engine-and-operators-v19.patch (text/x-patch, 343.8K) download
From 5094bb2b2f713a832e83cd165faca9c0470d01ae Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 2/4] Jsonpath engine and operators
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 20 +-
src/backend/utils/adt/float.c | 48 +-
src/backend/utils/adt/formatting.c | 43 +-
src/backend/utils/adt/json.c | 865 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 871 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2813 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 495 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 623 ++++++
src/backend/utils/adt/numeric.c | 294 ++-
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 5 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/elog.h | 19 +
src/include/utils/float.h | 7 +-
src/include/utils/formatting.h | 4 +-
src/include/utils/jsonapi.h | 63 +-
src/include/utils/jsonb.h | 39 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 106 +
src/include/utils/jsonpath_scanner.h | 30 +
src/include/utils/numeric.h | 9 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 800 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
41 files changed, 11869 insertions(+), 188 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_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
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 3a58bf6..92c881a 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 df7e01f..fffc791 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 0000000..7fab054
--- /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 20eead1..8db7f98 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 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 \
orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \
@@ -32,6 +33,23 @@ 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_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index c91bb1a..391b323 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/formatting.c b/src/backend/utils/adt/formatting.c
index 7cba489..43282bb 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3956,8 +3956,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;
@@ -3967,6 +3967,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)
{
@@ -3975,20 +3976,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")));
@@ -4052,20 +4060,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 f47a498..fb61d68 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;
}
@@ -1506,7 +1511,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 +1519,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 +1527,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 +1558,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 +1635,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),
@@ -2553,3 +2577,804 @@ 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,
+ &jbv->val.datetime.tz);
+
+ 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.c b/src/backend/utils/adt/jsonb.c
index 0ae9d7b..00a7f3a 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 713631b..8a45cb0 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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);
@@ -822,6 +822,7 @@ recurse:
/* 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
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,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.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..11d457d
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..e017ac1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2813 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#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
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Numeric (*func)(Numeric, Numeric, ErrorData **);
+ Numeric res;
+ bool hasNext;
+ ErrorData *edata;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add_internal;
+ break;
+ case jpiSub:
+ func = numeric_sub_internal;
+ break;
+ case jpiMul:
+ func = numeric_mul_internal;
+ break;
+ case jpiDiv:
+ func = numeric_div_internal;
+ break;
+ case jpiMod:
+ func = numeric_mod_internal;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ edata = NULL;
+ res = func(lval->val.numeric, rval->val.numeric, &edata);
+
+ if (edata)
+ return jperMakeErrorData(edata);
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = res;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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 *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, fmt, fmtlen, tzname, strict,
+ typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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_t 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = singleton ? jb : binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ ErrorData *edata = NULL;
+
+ if (JsonbType(jb) == jbvScalar)
+ jb = JsonbExtractScalar(jb->val.binary.data, &jbv);
+
+ if (jb->type == jbvNumeric)
+ {
+ /* 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 = float8_numeric_internal(val, &edata);
+ }
+ }
+ else
+ {
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ break;
+ }
+
+ if (edata)
+ {
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ThrowErrorData(edata);
+
+ FreeErrorData(edata);
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ else
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ if (tryToParseDatetime(template_str, template_len, datetime,
+ tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ {
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue idval;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbValue idstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+ int64_t id;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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 :
+#ifdef JSONPATH_JSON_C
+ (int64_t)((char *)((JsonContainer *) jb->val.binary.data)->data -
+ (char *) cxt->baseObject.jbc->data);
+#else
+ (int64_t)((char *) jb->val.binary.data -
+ (char *) cxt->baseObject.jbc);
+#endif
+ id += (int64_t) 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ 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 inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..3856a06
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..8101ffb
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,623 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 444e575..8893878 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 171fcc8..4ba9d60 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 788f881..1ef95c3 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index ce23c2f..e08057a 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3195,5 +3195,33 @@
{ 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' },
+{ 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 4026018..74f49e5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9103,6 +9103,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index d295eae..e7ae4cc 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 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/elog.h b/src/include/utils/elog.h
index 33c6b53..42a834c 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 05e1b27..d082bdc 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/formatting.h b/src/include/utils/formatting.h
index 208cc00..6db5b3f 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 6b483a1..6ef601f 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
@@ -161,6 +204,24 @@ 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);
+
+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 27873d4..22643de 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;
@@ -219,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
@@ -236,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,
};
/*
@@ -269,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 */
@@ -277,11 +288,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,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,
@@ -363,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);
@@ -379,5 +403,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 0000000..5d16131
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..064d77e
--- /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/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index cd8da8b..6e3e3f0 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_ */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..1c71984
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"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")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..66dea4b
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?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?
+-----------------------------------------------
+ {"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?
+-----------------------------------------------
+ {"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()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..193fc68
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 b5e1550..b07aced 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 49329ff..c0f488e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -157,6 +157,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000..824f510
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..43f34ef
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..8a3ea42
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 708579d..17c5fb6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -176,6 +176,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 68cf812..9998e16 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',
--
2.7.4
0003-Jsonpath-GIN-support-v19.patch (text/x-patch, 42.8K) download
From bcecf216b0e3411a37f9e7d83c682ce1b6c25e2c Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 3/4] Jsonpath GIN support
---
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 c8a2745..c11960c 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 075a54c..b2d226f 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 22643de..404ed70 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 5d16131..b3cf4c2 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 f045e08..92cf4c7 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 c073a5a..dd7b142 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 bd82fd1..1430a98 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;
--
2.7.4
0004-Jsonpath-syntax-extensions-v19.patch (text/x-patch, 46.4K) download
From 6ebc22fc8a120793e2acbcf41135e681cd57fa0a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:14 +0300
Subject: [PATCH 4/4] Jsonpath syntax extensions
---
src/backend/utils/adt/jsonpath.c | 153 ++++++++++-
src/backend/utils/adt/jsonpath_exec.c | 394 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 +++++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 +++++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 +++++
src/test/regress/sql/json_jsonpath.sql | 47 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1227 insertions(+), 78 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11d457d..456db2e 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -136,12 +136,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
nestingLevel + argNestingLevel,
insideArraySubscript);
@@ -218,6 +221,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -305,6 +363,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -494,12 +554,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -563,6 +623,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -585,7 +693,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -688,6 +796,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -699,6 +808,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -713,7 +832,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -765,7 +885,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -869,3 +992,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index e017ac1..853c899 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -83,6 +83,8 @@ static inline JsonPathExecResult recursiveExecuteNested(JsonPathExecContext *cxt
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1686,7 +1688,116 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{
int innermostArraySize = cxt->innermostArraySize;
int i;
@@ -1771,9 +1882,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -2051,7 +2166,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2062,84 +2176,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- if (tryToParseDatetime(template_str, template_len, datetime,
- tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tzname)
- pfree(tzname);
- }
- else
- {
- const char *templates[] = {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- int i;
-
- for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ if (tryToParseDatetime(template_str, template_len,
+ datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (tryToParseDatetime(templates[i], -1, datetime,
- NULL, true, &value, &typid,
- &typmod, &tz))
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
{
- res = jperOk;
- break;
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2269,6 +2412,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 3856a06..be1d488 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -255,6 +255,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -288,9 +308,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -314,7 +334,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -327,6 +347,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -381,6 +415,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index b3cf4c2..3747985 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -84,6 +84,10 @@ typedef enum JsonPathItemType {
jpiLast, /* LAST array subscript */
jpiStartsWith, /* STARTS WITH predicate */
jpiLikeRegex, /* LIKE_REGEX predicate */
+ jpiSequence, /* sequence constructor: 'expr, ...' */
+ jpiArray, /* array constructor: '[expr, ...]' */
+ jpiObject, /* object constructor: '{ key : value, ... }' */
+ jpiObjectField, /* element of object constructor: 'key : value' */
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -138,6 +142,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -164,6 +181,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -209,6 +229,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 1c71984..86e553d 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 66dea4b..1d3215b 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 193fc68..ea29105 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 824f510..0901876 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 43f34ef..ad7a320 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 8a3ea42..653f928 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-06 15:48 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2018-11-06 15:48 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 11/6/18 3:31 PM, Nikita Glukhov wrote:
> On 29.10.2018 2:20, Tomas Vondra wrote:>
>
> ...>
> Thank you for your review.
>
>> 1) There are no docs, which makes it pretty much non-committable for
>> now. I know there is [1] and it was a good intro for the review, but we
>> need something as a part of our sgml docs.
>
> I think that jsonpath part of documentation can be extracted from [2] and
> added to the patch set.
>
Yes, please let's do that. The patch could not get into RFC without
docs, so let's deal with it now.
>> 2) 0001 says this:
>>
>> *typmod = -1; /* TODO implement FF1, ..., FF9 */
>>
>> but I have no idea what FF1 or FF9 are. I guess it needs to be
>> implemented, or explained better.
>
> FF1-FF9 are standard datetime template fields used for specifying of fractional
> seconds. FF3/FF6 are analogues of our MS/US. I decided simply to implement
> this feature (see patch 0001, I also can supply it in the separate patch).
>
Understood. Thanks for the explanation.
> But FF7-FF9 are questionable since the maximal supported precision is only 6.
> They are optional by the standard:
>
> 95) Specifications for Feature F555, “Enhanced seconds precision”:
> d) Subclause 9.44, “Datetime templates”:
> i) Without Feature F555, “Enhanced seconds precision”,
> a <datetime template fraction> shall not be FF7, FF8 or FF9.
>
> So I decided to allow FF7-FF9 only on the output in to_char().
>
Hmmmm, isn't that against the standard then? I mean, if our precision is
only 6, that probably means we don't have the F555 feature, which means
FF7-9 should not be available.
It also seems like a bit surprising behavior that we only allow those
fields in to_char() and not in other places.
>> 4) There probably should be .gitignore rule for jsonpath_gram.h, just
>> like for other generated header files.
>
> I see 3 rules in /src/backend/utils/adt/.gitignore:
>
> /jsonpath_gram.h
> /jsonpath_gram.c
> /jsonpath_scan.c
>
But there's a symlink in src/include/utils/jsonpath_gram.h and that's
not in .gitignore.
>> FWIW, I'd use JSONPATH_STRICT instead of JSONPATH_LAX. The rest of the
>> codebase works with "strict" flags passed around, and it's easy to
>> forget to negate the flag somewhere (at least that's my experience).
>
> Jsonpath lax/strict mode flag is used only in executeJsonPath() where it is
> saved in "laxMode" field. New "strict" flag passed to datetime functions
> is unrelated to jsonpath.
>
OK.
>> 7) I see src/include/utils/jsonpath_json.h adds support for plain json
>> by undefining various jsonb macros and redirecting them to the json
>> variants. I find that rather suspicious - imagine you're investigating
>> something in code using those jsonb macros, and wondering why it ends up
>> calling the json stuff. I'd be pretty confused ...
>
> I agree, this is rather simple but doubtful solution. That's why json support
> was in a separate patch until the 18th version of the patches.
>
> But if we do not want to compile jsonpath.c twice with different definitions,
> then we need some kind of run-time wrapping over json strings and jsonb
> containers, which seems a bit harder to implement.
>
> To simplify debugging I can also suggest to explicitly preprocess jsonpath.c
> into jsonpath_json.c using redefinitions from jsonpath_json.h before its
> compilation.
>
Not sure what's the right solution. But I agree the current approach
probably is not it.
>>
>> 9) It's generally a good idea to make the individual pieces committable
>> separately, but that means e.g. the regression tests have to pass after
>> each patch. At the moment that does not seem to be the case for 0002,
>> see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
>> not sure if that's related.
>
> This should definitely be a bug in json support, but I can't reproduce
> it simply by defining -DRANDOMIZE_ALLOCATED_MEMORY. Could you provide
> a stack trace at least?
>
I'll try.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-08 01:52 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2018-11-08 01:52 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 11/6/18 4:48 PM, Tomas Vondra wrote:
> On 11/6/18 3:31 PM, Nikita Glukhov wrote:
>> On 29.10.2018 2:20, Tomas Vondra wrote:>
>>
>> ...
>>>
>>> 9) It's generally a good idea to make the individual pieces committable
>>> separately, but that means e.g. the regression tests have to pass after
>>> each patch. At the moment that does not seem to be the case for 0002,
>>> see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
>>> not sure if that's related.
>>
>> This should definitely be a bug in json support, but I can't reproduce
>> it simply by defining -DRANDOMIZE_ALLOCATED_MEMORY. Could you provide
>> a stack trace at least?
>>
> I'll try.
>
Not sure why you can't reproduce the failures, it's perfectly
reproducible for me. For the record, I'm doing this:
./configure --prefix=/home/user/pg-jsonpath --enable-debug
--enable-cassert CFLAGS="-O0 -DRANDOMIZE_ALLOCATED_MEMORY" && make -s
clean && make -s -j4 && make check
After sticking Assert(false) to JsonEncodeJsonbValue (to the default
case), I get a failure like this:
select json '{}' @* 'lax $[0]';
! WARNING: unknown jsonb value type: 20938064
! server closed the connection unexpectedly
! This probably means the server terminated abnormally
! before or while processing the request.
! connection to server was lost
The backtrace is attached. My guess is JsonValueListGetList in
jsonb_jsonpath_query only does shallow copy instead of copying the
pieces into funcctx->multi_call_memory_ctx, so it gets corrupted on
subsequent calls.
I also attach valgrind report, but I suppose the reported issues are a
consequence of the same bug.
regard
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
jsonpath-bt.txt (text/plain, 11.1K) download
(gdb) bt
#0 0x00007aa8bd7ddeab in raise () from /lib64/libc.so.6
#1 0x00007aa8bd7c85b9 in abort () from /lib64/libc.so.6
#2 0x0000000000a34154 in ExceptionalCondition (conditionName=0xc6d888 "!(0)", errorType=0xc6ce18 "FailedAssertion", fileName=0xc6caa0 "json.c", lineNumber=3047) at assert.c:54
#3 0x00000000009415e7 in JsonEncodeJsonbValue (buf=0x7ffca408b870, jbv=0x7ffca408b830) at json.c:3047
#4 0x00000000009416a4 in JsonbValueToJson (jbv=0x7ffca408b830) at json.c:3077
#5 0x000000000096707c in jsonb_jsonpath_query (fcinfo=0x13f4cf0) at jsonpath_exec.c:2735
#6 0x00000000009670bb in json_jsonpath_query2 (fcinfo=0x13f4cf0) at jsonpath_exec.c:2741
#7 0x00000000006e1c18 in ExecMakeFunctionResultSet (fcache=0x13f4c80, econtext=0x13f41b8, argContext=0x13f5d40, isNull=0x13f4be0, isDone=0x13f4c60) at execSRF.c:604
#8 0x000000000070d858 in ExecProjectSRF (node=0x13f40a0, continuing=false) at nodeProjectSet.c:175
#9 0x000000000070d715 in ExecProjectSet (pstate=0x13f40a0) at nodeProjectSet.c:105
#10 0x00000000006dee78 in ExecProcNodeFirst (node=0x13f40a0) at execProcnode.c:445
#11 0x00000000006d3e56 in ExecProcNode (node=0x13f40a0) at ../../../src/include/executor/executor.h:237
#12 0x00000000006d66fa in ExecutePlan (estate=0x13f3e48, planstate=0x13f40a0, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x13fa1b0, execute_once=true) at execMain.c:1707
#13 0x00000000006d447d in standard_ExecutorRun (queryDesc=0x1353768, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:364
#14 0x00000000006d42ab in ExecutorRun (queryDesc=0x1353768, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:307
#15 0x00000000008be5ff in PortalRunSelect (portal=0x1397348, forward=true, count=0, dest=0x13fa1b0) at pquery.c:932
#16 0x00000000008be2a4 in PortalRun (portal=0x1397348, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x13fa1b0, altdest=0x13fa1b0, completionTag=0x7ffca408be70 "") at pquery.c:773
#17 0x00000000008b8271 in exec_simple_query (query_string=0x1330738 "select json '{}' @* 'lax $[0]';") at postgres.c:1215
#18 0x00000000008bc580 in PostgresMain (argc=1, argv=0x135bab0, dbname=0x135b820 "regression", username=0x132d028 "user") at postgres.c:4243
#19 0x0000000000819f7a in BackendRun (port=0x1354940) at postmaster.c:4377
#20 0x0000000000819748 in BackendStartup (port=0x1354940) at postmaster.c:4068
#21 0x0000000000815c60 in ServerLoop () at postmaster.c:1700
#22 0x000000000081550c in PostmasterMain (argc=8, argv=0x132aeb0) at postmaster.c:1373
#23 0x000000000073ebba in main (argc=8, argv=0x132aeb0) at main.c:228
(gdb) up
#4 0x00000000009416a4 in JsonbValueToJson (jbv=0x7ffca408b830) at json.c:3077
3077 JsonEncodeJsonbValue(&buf, jbv);
(gdb) print *jbv
$1 = {type = 20938064, val = {numeric = 0x7ffca408b870, boolean = 112, string = {len = -1542932368, val = 0x0}, array = {nElems = -1542932368, elems = 0x0, rawScalar = 16}, object = {nPairs = -1542932368, uniquified = 252, pairs = 0x0}, binary = {len = -1542932368, data = 0x0}, datetime = {
value = 140723060521072, typid = 0, typmod = 0, tz = 4684816}}}
(gdb) up
#5 0x000000000096707c in jsonb_jsonpath_query (fcinfo=0x13f4cf0) at jsonpath_exec.c:2735
2735 SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
(gdb) print *v
$2 = {type = 20938064, val = {numeric = 0x7ffca408b870, boolean = 112, string = {len = -1542932368, val = 0x0}, array = {nElems = -1542932368, elems = 0x0, rawScalar = 16}, object = {nPairs = -1542932368, uniquified = 252, pairs = 0x0}, binary = {len = -1542932368, data = 0x0}, datetime = {
value = 140723060521072, typid = 0, typmod = 0, tz = 4684816}}}
(gdb) bt full
#0 0x00007aa8bd7ddeab in raise () from /lib64/libc.so.6
No symbol table info available.
#1 0x00007aa8bd7c85b9 in abort () from /lib64/libc.so.6
No symbol table info available.
#2 0x0000000000a34154 in ExceptionalCondition (conditionName=0xc6d888 "!(0)", errorType=0xc6ce18 "FailedAssertion", fileName=0xc6caa0 "json.c", lineNumber=3047) at assert.c:54
No locals.
#3 0x00000000009415e7 in JsonEncodeJsonbValue (buf=0x7ffca408b870, jbv=0x7ffca408b830) at json.c:3047
__func__ = "JsonEncodeJsonbValue"
#4 0x00000000009416a4 in JsonbValueToJson (jbv=0x7ffca408b830) at json.c:3077
buf = {data = 0x13f7e90 "", len = 0, maxlen = 1024, cursor = 0}
json = 0x13f7e68
type = 0
size = 7634563
#5 0x000000000096707c in jsonb_jsonpath_query (fcinfo=0x13f4cf0) at jsonpath_exec.c:2735
rsi = 0x7ffca408b9c0
funcctx = 0x1352708
found = 0x13528e0
v = 0x7ffca408b830
c = 0x13528b8
#6 0x00000000009670bb in json_jsonpath_query2 (fcinfo=0x13f4cf0) at jsonpath_exec.c:2741
No locals.
#7 0x00000000006e1c18 in ExecMakeFunctionResultSet (fcache=0x13f4c80, econtext=0x13f41b8, argContext=0x13f5d40, isNull=0x13f4be0, isDone=0x13f4c60) at execSRF.c:604
arguments = 0x13f5970
result = 20938064
fcinfo = 0x13f4cf0
fcusage = {fs = 0x0, save_f_total_time = {tv_sec = 20923840, tv_nsec = 0}, save_total = {tv_sec = 20921648, tv_nsec = 140723060521568}, f_start = {tv_sec = 7398875, tv_nsec = 114}}
rsinfo = {type = T_ReturnSetInfo, econtext = 0x13f41b8, expectedDesc = 0x13fe1f0, allowedModes = 3, returnMode = SFRM_ValuePerCall, isDone = ExprMultipleResult, setResult = 0x0, setDesc = 0x0}
callit = true
i = 2
__func__ = "ExecMakeFunctionResultSet"
#8 0x000000000070d858 in ExecProjectSRF (node=0x13f40a0, continuing=false) at nodeProjectSet.c:175
elem = 0x13f4c80
isdone = 0x13f4c60
result = 0x13f4bd8
isnull = 0x13f4be0
resultSlot = 0x13f4b80
econtext = 0x13f41b8
oldcontext = 0x13f3d30
hassrf = false
hasresult = false
argno = 0
#9 0x000000000070d715 in ExecProjectSet (pstate=0x13f40a0) at nodeProjectSet.c:105
node = 0x13f40a0
outerTupleSlot = 0x13f44c0
resultSlot = 0x8bb308 <check_stack_depth+12>
outerPlan = 0x13f42b0
econtext = 0x13f41b8
#10 0x00000000006dee78 in ExecProcNodeFirst (node=0x13f40a0) at execProcnode.c:445
No locals.
#11 0x00000000006d3e56 in ExecProcNode (node=0x13f40a0) at ../../../src/include/executor/executor.h:237
No locals.
#12 0x00000000006d66fa in ExecutePlan (estate=0x13f3e48, planstate=0x13f40a0, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x13fa1b0, execute_once=true) at execMain.c:1707
slot = 0x1397348
current_tuple_count = 0
#13 0x00000000006d447d in standard_ExecutorRun (queryDesc=0x1353768, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:364
estate = 0x13f3e48
operation = CMD_SELECT
dest = 0x13fa1b0
sendTuples = true
oldcontext = 0x1353650
__func__ = "standard_ExecutorRun"
#14 0x00000000006d42ab in ExecutorRun (queryDesc=0x1353768, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:307
No locals.
#15 0x00000000008be5ff in PortalRunSelect (portal=0x1397348, forward=true, count=0, dest=0x13fa1b0) at pquery.c:932
queryDesc = 0x1353768
direction = ForwardScanDirection
nprocessed = 140723060522208
__func__ = "PortalRunSelect"
#16 0x00000000008be2a4 in PortalRun (portal=0x1397348, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x13fa1b0, altdest=0x13fa1b0, completionTag=0x7ffca408be70 "") at pquery.c:773
save_exception_stack = 0x7ffca408bfb0
save_context_stack = 0x0
local_sigjmp_buf = {{__jmpbuf = {0, 3529150028062792428, 4684816, 140723060524240, 0, 0, 3529150027981003500, -3529900415750235412}, __mask_was_saved = 0, __saved_mask = {__val = {128, 20121120, 64, 20264936, 15304, 20264528, 20946272, 140723060522496, 10934220, 20553864, 112,
---Type <return> to continue, or q <return> to quit---
20947488, 112, 2, 20947376, 20121120}}}}
result = false
nprocessed = 0
saveTopTransactionResourceOwner = 0x1362738
saveTopTransactionContext = 0x13b43c0
saveActivePortal = 0x0
saveResourceOwner = 0x1362738
savePortalContext = 0x0
saveMemoryContext = 0x13b43c0
__func__ = "PortalRun"
#17 0x00000000008b8271 in exec_simple_query (query_string=0x1330738 "select json '{}' @* 'lax $[0]';") at postgres.c:1215
parsetree = 0x1331610
portal = 0x1397348
snapshot_set = true
commandTag = 0xc5a975 "SELECT"
completionTag = "\000\000\000\000\002\000\000\000\b\342\244", '\000' <repeats 13 times>, "P\v3\001\037\000\000\000\300\276\b\244\374\177\000\000\070\351\244\000\000\000\000\000B", '\000' <repeats 14 times>
querytree_list = 0x13f24c8
plantree_list = 0x13fa178
receiver = 0x13fa1b0
format = 0
dest = DestRemote
oldcontext = 0x13b43c0
parsetree_list = 0x1331670
parsetree_item = 0x1331648
save_log_statement_stats = false
was_logged = false
use_implicit_block = false
msec_str = "\000\277\b\244\374\177\000\000\022\270\244\000\000\000\000\000\006\000\000\000\037\000\000\000\070\a3\001\000\000\000"
__func__ = "exec_simple_query"
#18 0x00000000008bc580 in PostgresMain (argc=1, argv=0x135bab0, dbname=0x135b820 "regression", username=0x132d028 "user") at postgres.c:4243
query_string = 0x1330738 "select json '{}' @* 'lax $[0]';"
firstchar = 81
input_message = {data = 0x1330738 "select json '{}' @* 'lax $[0]';", len = 32, maxlen = 1024, cursor = 32}
local_sigjmp_buf = {{__jmpbuf = {0, 3529150027347663596, 4684816, 140723060524240, 0, 0, 3529150028098444012, -3529900414674527508}, __mask_was_saved = 1, __saved_mask = {__val = {0, 20126752, 1024, 20121120, 0, 0, 10888111, 1024, 20126776, 140723060523136, 10892266, 20126776,
20121120, 30084897848, 20126776, 20126752}}}}
send_ready_for_query = false
disable_idle_in_transaction_timeout = false
__func__ = "PostgresMain"
#19 0x0000000000819f7a in BackendRun (port=0x1354940) at postmaster.c:4377
av = 0x135bab0
maxac = 2
ac = 1
i = 1
__func__ = "BackendRun"
#20 0x0000000000819748 in BackendStartup (port=0x1354940) at postmaster.c:4068
bn = 0x1354590
pid = 0
__func__ = "BackendStartup"
#21 0x0000000000815c60 in ServerLoop () at postmaster.c:1700
port = 0x1354940
i = 0
rmask = {fds_bits = {16, 0 <repeats 15 times>}}
selres = 1
now = 1541640685
readmask = {fds_bits = {16, 0 <repeats 15 times>}}
nSockets = 5
last_lockfile_recheck_time = 1541640684
last_touch_time = 1541640684
__func__ = "ServerLoop"
#22 0x000000000081550c in PostmasterMain (argc=8, argv=0x132aeb0) at postmaster.c:1373
opt = -1
status = 0
userDoption = 0x134d120 "/home/user/work/postgres/src/test/regress/./tmp_check/data"
listen_addr_saved = false
i = 64
output_config_variable = 0x0
__func__ = "PostmasterMain"
#23 0x000000000073ebba in main (argc=8, argv=0x132aeb0) at main.c:228
do_check_root = true
jsonpath-valgrind.txt (text/plain, 11.0K) download
==4829== Conditional jump or move depends on uninitialised value(s)
==4829== at 0x941E53: JsonbValueToJson (json.c:3063)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== by 0x8BEAE2: PortalRun (pquery.c:773)
==4829== by 0x8B8AAF: exec_simple_query (postgres.c:1215)
==4829== by 0x8BCDBE: PostgresMain (postgres.c:4243)
==4829== by 0x81A2B0: BackendRun (postmaster.c:4377)
==4829== by 0x819A7E: BackendStartup (postmaster.c:4068)
==4829== by 0x815F96: ServerLoop (postmaster.c:1700)
==4829== by 0x815842: PostmasterMain (postmaster.c:1373)
==4829== by 0x73EEF0: main (main.c:228)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941E46: JsonbValueToJson (json.c:3059)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
fun:PortalRun
fun:exec_simple_query
fun:PostgresMain
fun:BackendRun
fun:BackendStartup
fun:ServerLoop
fun:PostmasterMain
fun:main
}
==4829== Conditional jump or move depends on uninitialised value(s)
==4829== at 0x941A7C: JsonEncodeJsonbValue (json.c:2951)
==4829== by 0x941EE2: JsonbValueToJson (json.c:3077)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== by 0x8BEAE2: PortalRun (pquery.c:773)
==4829== by 0x8B8AAF: exec_simple_query (postgres.c:1215)
==4829== by 0x8BCDBE: PostgresMain (postgres.c:4243)
==4829== by 0x81A2B0: BackendRun (postmaster.c:4377)
==4829== by 0x819A7E: BackendStartup (postmaster.c:4068)
==4829== by 0x815F96: ServerLoop (postmaster.c:1700)
==4829== by 0x815842: PostmasterMain (postmaster.c:1373)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941A52: JsonEncodeJsonbValue (json.c:2948)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:JsonEncodeJsonbValue
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
fun:PortalRun
fun:exec_simple_query
fun:PostgresMain
fun:BackendRun
fun:BackendStartup
fun:ServerLoop
fun:PostmasterMain
}
==4829== Conditional jump or move depends on uninitialised value(s)
==4829== at 0xA9BEF7: adjust_sign (snprintf.c:1436)
==4829== by 0xA9B533: fmtint (snprintf.c:1050)
==4829== by 0xA9A70C: dopr (snprintf.c:614)
==4829== by 0xA99C28: pg_vsnprintf (snprintf.c:195)
==4829== by 0xAA0C48: pvsnprintf (psprintf.c:110)
==4829== by 0x72ACBB: appendStringInfoVA (stringinfo.c:136)
==4829== by 0xA379E0: elog_finish (elog.c:1359)
==4829== by 0x941E0C: JsonEncodeJsonbValue (json.c:3046)
==4829== by 0x941EE2: JsonbValueToJson (json.c:3077)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941A52: JsonEncodeJsonbValue (json.c:2948)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:adjust_sign
fun:fmtint
fun:dopr
fun:pg_vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:elog_finish
fun:JsonEncodeJsonbValue
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
}
==4829== Conditional jump or move depends on uninitialised value(s)
==4829== at 0xA9B55B: fmtint (snprintf.c:1059)
==4829== by 0xA9A70C: dopr (snprintf.c:614)
==4829== by 0xA99C28: pg_vsnprintf (snprintf.c:195)
==4829== by 0xAA0C48: pvsnprintf (psprintf.c:110)
==4829== by 0x72ACBB: appendStringInfoVA (stringinfo.c:136)
==4829== by 0xA379E0: elog_finish (elog.c:1359)
==4829== by 0x941E0C: JsonEncodeJsonbValue (json.c:3046)
==4829== by 0x941EE2: JsonbValueToJson (json.c:3077)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== by 0x8BEAE2: PortalRun (pquery.c:773)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941A52: JsonEncodeJsonbValue (json.c:2948)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:fmtint
fun:dopr
fun:pg_vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:elog_finish
fun:JsonEncodeJsonbValue
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
fun:PortalRun
}
==4829== Use of uninitialised value of size 8
==4829== at 0xA9B59B: fmtint (snprintf.c:1066)
==4829== by 0xA9A70C: dopr (snprintf.c:614)
==4829== by 0xA99C28: pg_vsnprintf (snprintf.c:195)
==4829== by 0xAA0C48: pvsnprintf (psprintf.c:110)
==4829== by 0x72ACBB: appendStringInfoVA (stringinfo.c:136)
==4829== by 0xA379E0: elog_finish (elog.c:1359)
==4829== by 0x941E0C: JsonEncodeJsonbValue (json.c:3046)
==4829== by 0x941EE2: JsonbValueToJson (json.c:3077)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== by 0x8BEAE2: PortalRun (pquery.c:773)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941A52: JsonEncodeJsonbValue (json.c:2948)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Value8
fun:fmtint
fun:dopr
fun:pg_vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:elog_finish
fun:JsonEncodeJsonbValue
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
fun:PortalRun
}
==4829== Conditional jump or move depends on uninitialised value(s)
==4829== at 0xA9B5B8: fmtint (snprintf.c:1068)
==4829== by 0xA9A70C: dopr (snprintf.c:614)
==4829== by 0xA99C28: pg_vsnprintf (snprintf.c:195)
==4829== by 0xAA0C48: pvsnprintf (psprintf.c:110)
==4829== by 0x72ACBB: appendStringInfoVA (stringinfo.c:136)
==4829== by 0xA379E0: elog_finish (elog.c:1359)
==4829== by 0x941E0C: JsonEncodeJsonbValue (json.c:3046)
==4829== by 0x941EE2: JsonbValueToJson (json.c:3077)
==4829== by 0x9678BA: jsonb_jsonpath_query (jsonpath_exec.c:2735)
==4829== by 0x9678F9: json_jsonpath_query2 (jsonpath_exec.c:2741)
==4829== by 0x6E1F4E: ExecMakeFunctionResultSet (execSRF.c:604)
==4829== by 0x70DB8E: ExecProjectSRF (nodeProjectSet.c:175)
==4829== by 0x70DA4B: ExecProjectSet (nodeProjectSet.c:105)
==4829== by 0x6DF1AE: ExecProcNodeFirst (execProcnode.c:445)
==4829== by 0x6D418C: ExecProcNode (executor.h:237)
==4829== by 0x6D6A30: ExecutePlan (execMain.c:1707)
==4829== by 0x6D47B3: standard_ExecutorRun (execMain.c:364)
==4829== by 0x6D45E1: ExecutorRun (execMain.c:307)
==4829== by 0x8BEE3D: PortalRunSelect (pquery.c:932)
==4829== by 0x8BEAE2: PortalRun (pquery.c:773)
==4829== Uninitialised value was created by a stack allocation
==4829== at 0x941A52: JsonEncodeJsonbValue (json.c:2948)
==4829==
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:fmtint
fun:dopr
fun:pg_vsnprintf
fun:pvsnprintf
fun:appendStringInfoVA
fun:elog_finish
fun:JsonEncodeJsonbValue
fun:JsonbValueToJson
fun:jsonb_jsonpath_query
fun:json_jsonpath_query2
fun:ExecMakeFunctionResultSet
fun:ExecProjectSRF
fun:ExecProjectSet
fun:ExecProcNodeFirst
fun:ExecProcNode
fun:ExecutePlan
fun:standard_ExecutorRun
fun:ExecutorRun
fun:PortalRunSelect
fun:PortalRun
}
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-08 01:57 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2018-11-08 01:57 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
BTW is the v19 really just a rebase of the preceding version?
I'm asking because v18 was adding two types into pg_type.dat, namely
jsonpath (6050) and _jsonpath (6051), while v19 only adds jsonpath
(6050). I've noticed because v18 was missing a comma between the two
entries, which I'd say is a bug - interestingly enough, both types were
created correctly, so it seems to be resilient to such typos.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-08 05:12 Tom Lane <tgl@sss.pgh.pa.us>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Tom Lane @ 2018-11-08 05:12 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
> BTW is the v19 really just a rebase of the preceding version?
> I'm asking because v18 was adding two types into pg_type.dat, namely
> jsonpath (6050) and _jsonpath (6051), while v19 only adds jsonpath
> (6050).
I haven't looked at this patch, but manual generation of array type entries
is obsolete since 3dc820c43e427371b66d217f2bd5481fc9ef2e2d. There should
still be a mention of the OID to use for the array type though ...
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-08 11:22 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 198+ messages in thread
From: Tomas Vondra @ 2018-11-08 11:22 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 11/8/18 6:12 AM, Tom Lane wrote:
> Tomas Vondra <tomas.vondra@2ndquadrant.com> writes:
>> BTW is the v19 really just a rebase of the preceding version?
>> I'm asking because v18 was adding two types into pg_type.dat, namely
>> jsonpath (6050) and _jsonpath (6051), while v19 only adds jsonpath
>> (6050).
>
> I haven't looked at this patch, but manual generation of array type entries
> is obsolete since 3dc820c43e427371b66d217f2bd5481fc9ef2e2d. There should
> still be a mention of the OID to use for the array type though ...
>
Ah, I missed this improvement somehow. (Yes, the array type OID is still
mentioned as part of the type definition.)
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-09 15:50 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-11-09 15:50 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 20th version the jsonpath patches.
On 08.11.2018 4:52, Tomas Vondra wrote:
> On 11/6/18 4:48 PM, Tomas Vondra wrote:
>> On 11/6/18 3:31 PM, Nikita Glukhov wrote:
>>> On 29.10.2018 2:20, Tomas Vondra wrote:>
>>>> 9) It's generally a good idea to make the individual pieces committable
>>>> separately, but that means e.g. the regression tests have to pass after
>>>> each patch. At the moment that does not seem to be the case for 0002,
>>>> see the attached file. I'm running with -DRANDOMIZE_ALLOCATED_MEMORY,
>>>> not sure if that's related.
>>> This should definitely be a bug in json support, but I can't reproduce
>>> it simply by defining -DRANDOMIZE_ALLOCATED_MEMORY. Could you provide
>>> a stack trace at least?
>> I'll try.
> Not sure why you can't reproduce the failures, it's perfectly
> reproducible for me. For the record, I'm doing this:
>
> ./configure --prefix=/home/user/pg-jsonpath --enable-debug
> --enable-cassert CFLAGS="-O0 -DRANDOMIZE_ALLOCATED_MEMORY" && make -s
> clean && make -s -j4 && make check
>
> After sticking Assert(false) to JsonEncodeJsonbValue (to the default
> case), I get a failure like this:
>
> select json '{}' @* 'lax $[0]';
> ! WARNING: unknown jsonb value type: 20938064
> ! server closed the connection unexpectedly
> ! This probably means the server terminated abnormally
> ! before or while processing the request.
> ! connection to server was lost
>
> The backtrace is attached. My guess is JsonValueListGetList in
> jsonb_jsonpath_query only does shallow copy instead of copying the
> pieces into funcctx->multi_call_memory_ctx, so it gets corrupted on
> subsequent calls.
>
> I also attach valgrind report, but I suppose the reported issues are a
> consequence of the same bug.
Than you for your help, I finally have managed to reproduce this bug.
The problem was really related to copying values but it was not located in the
jsonb_jsonpath_query() where the whole value list should already be allocated in
funcctx->multi_call_memory_ctx (executeJsonPath() is called in that context and
also input json/jsonb is copied into it). Stack-allocated root JsonbValue was
not copied to the heap due to wrong copy condition in recursiveExecuteNoUnwrap()
(jpiIndexArray case):
@@ -1754,1 +1754,1 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, ...
- res = recursiveExecuteNext(cxt, jsp, &elem, v, found, !binary);
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found, singleton || !binary);
I have refactored a bit this place extracting explicit 'copy' flag.
Also I found a similar problem in makePassingVars() where Numerics were not
copied into 'multi_call_memory_ctx' from the input json object. I decided to use
datumCopy() inside makePassingVars() instead of copying the whole input json in
jsonb_jsonpath_query() using PG_GETARG_JSONB_P_COPY(). But I think that
processing of the input json with variables should ideally be bone lazily,
just don't know if this refactoring is worth it because 3rd argument of
json[b]_jsonpath_xxx() functions is used only for testing of variables passing,
SQL/JSON functions use the different mechanism.
On 06.11.2018 18:48, Tomas Vondra wrote:
>> But FF7-FF9 are questionable since the maximal supported precision is only 6.
>> They are optional by the standard:
>> 95) Specifications for Feature F555, “Enhanced seconds precision”:
>> d) Subclause 9.44, “Datetime templates”:
>> i) Without Feature F555, “Enhanced seconds precision”,
>> a <datetime template fraction> shall not be FF7, FF8 or FF9.
>> So I decided to allow FF7-FF9 only on the output in to_char().
> Hmmmm, isn't that against the standard then? I mean, if our precision is
> only 6, that probably means we don't have the F555 feature, which means
> FF7-9 should not be available.
>
> It also seems like a bit surprising behavior that we only allow those
> fields in to_char() and not in other places.
Ok, now error is thrown if FF7-FF9 are found in the format string.
On 06.11.2018 18:48, Tomas Vondra wrote:
>>> 4) There probably should be .gitignore rule for jsonpath_gram.h, just
>>> like for other generated header files.
>> I see 3 rules in/src/backend/utils/adt/.gitignore:
>>
>> /jsonpath_gram.h
>> /jsonpath_gram.c
>> /jsonpath_scan.c
> But there's a symlink in src/include/utils/jsonpath_gram.h and that's
> not in .gitignore.
Added jsonpath_gram.h to/src/include/utils/.gitignore.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (4 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v20.patch (text/x-patch, 32.0K) download
From 55098b8aa492223d1f67bac64fc4386ad2aa501a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 1/4] Preliminary datetime infrastructure
---
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 434 +++++++++++++++++++++++++++---
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 86 ++++++
src/test/regress/expected/timestamp.out | 15 ++
src/test/regress/expected/timestamptz.out | 15 ++
src/test/regress/sql/horology.sql | 14 +
src/test/regress/sql/timestamp.sql | 8 +
src/test/regress/sql/timestamptz.sql | 8 +
12 files changed, 562 insertions(+), 40 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cb6b5e55..7d5c8ac 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 2923afe..7cba489 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,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,
@@ -645,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,
@@ -745,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},
@@ -794,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},
@@ -895,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
@@ -962,6 +1000,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1019,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1037,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,
- 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, 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);
@@ -2514,18 +2557,41 @@ 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;
+ case DCH_FF7:
+ DCH_to_char_fsec("%06d0", in->fsec);
break;
+ case DCH_FF8:
+ DCH_to_char_fsec("%06d00", in->fsec);
+ break;
+ case DCH_FF9: /* nanosecond */
+ DCH_to_char_fsec("%06d000", in->fsec);
+ break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,13 +3073,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;
@@ -3149,8 +3217,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3164,6 +3242,9 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
from_char_parse_int(&out->ssss, &s, n);
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF7:
+ case DCH_FF8:
+ case DCH_FF9:
case DCH_tz:
case DCH_TZ:
case DCH_OF:
@@ -3374,6 +3455,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")));
+ }
}
/*
@@ -3394,6 +3492,112 @@ 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_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;
+ 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)
@@ -3682,8 +3886,10 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3907,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);
}
@@ -3718,7 +3928,8 @@ 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, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
+ &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3951,160 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * 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 fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, fmt_len, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+
+ 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
*
* 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
@@ -3751,14 +4112,20 @@ 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, const char *fmt_str, int fmt_len, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
- int fmt_len;
+ char *fmt_tmp = NULL;
char *date_str;
int fmask;
@@ -3769,15 +4136,15 @@ do_to_timestamp(text *date_txt, text *fmt,
*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)
{
/*
@@ -3807,13 +4174,18 @@ 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);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
- pfree(fmt_str);
if (!incache)
pfree(format);
}
+ if (fmt_tmp)
+ pfree(fmt_tmp);
+
DEBUG_TMFC(&tmfc);
/*
@@ -3991,6 +4363,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 449164a..fcc6d23 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 eb6d2a1..10cc822 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 de9e9ad..165f0e7 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 a9f5548..208cc00 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
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..f103d36 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: formatting field "FF7" is only supported in to_char
+SELECT to_timestamp('123', 'FF8');
+ERROR: formatting field "FF8" is only supported in to_char
+SELECT to_timestamp('123', 'FF9');
+ERROR: formatting field "FF9" is only supported in to_char
--
-- 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 4a2fabd..4742f0b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 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 0000000 00000000 000000000 000 000000
+ | 7 78 780 7800 78000 780000 7800000 78000000 780000000 780 780000
+ | 7 78 789 7890 78901 789010 7890100 78901000 789010000 789 789010
+ | 7 78 789 7890 78901 789012 7890120 78901200 789012000 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2340f30..7d73f49 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1699,6 +1699,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 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 0000000 00000000 000000000 000 000000
+ | 7 78 780 7800 78000 780000 7800000 78000000 780000000 780 780000
+ | 7 78 789 7890 78901 789010 7890100 78901000 789010000 789 789010
+ | 7 78 789 7890 78901 789012 7890120 78901200 789012000 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..ef34323 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 b7957cb..ada7bc1 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index f17d153..e1ef747 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -248,6 +248,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 FF7 FF8 FF9 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
--
2.7.4
0002-Jsonpath-engine-and-operators-v20.patch (text/x-patch, 343.8K) download
From 5094bb2b2f713a832e83cd165faca9c0470d01ae Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 2/4] Jsonpath engine and operators
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 20 +-
src/backend/utils/adt/float.c | 48 +-
src/backend/utils/adt/formatting.c | 43 +-
src/backend/utils/adt/json.c | 865 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 871 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2813 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 495 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 623 ++++++
src/backend/utils/adt/numeric.c | 294 ++-
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 5 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/elog.h | 19 +
src/include/utils/float.h | 7 +-
src/include/utils/formatting.h | 4 +-
src/include/utils/jsonapi.h | 63 +-
src/include/utils/jsonb.h | 39 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 106 +
src/include/utils/jsonpath_scanner.h | 30 +
src/include/utils/numeric.h | 9 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 800 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
41 files changed, 11869 insertions(+), 188 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_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
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 3a58bf6..92c881a 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 df7e01f..fffc791 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 0000000..7fab054
--- /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 20eead1..8db7f98 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 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 \
orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \
@@ -32,6 +33,23 @@ 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_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index c91bb1a..391b323 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/formatting.c b/src/backend/utils/adt/formatting.c
index 7cba489..43282bb 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -3956,8 +3956,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;
@@ -3967,6 +3967,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)
{
@@ -3975,20 +3976,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")));
@@ -4052,20 +4060,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 f47a498..fb61d68 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;
}
@@ -1506,7 +1511,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 +1519,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 +1527,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 +1558,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 +1635,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),
@@ -2553,3 +2577,804 @@ 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,
+ &jbv->val.datetime.tz);
+
+ 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.c b/src/backend/utils/adt/jsonb.c
index 0ae9d7b..00a7f3a 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 713631b..8a45cb0 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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);
@@ -822,6 +822,7 @@ recurse:
/* 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
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,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.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..11d457d
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..e017ac1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2813 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "regex/regex.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+#include "utils/formatting.h"
+#include "utils/json.h"
+#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
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ if (var == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("could not find '%s' passed variable",
+ pnstrdup(varName, varNameLength))));
+
+ computedValue = var->cb(var->cb_arg, &isNull);
+
+ if (isNull)
+ {
+ value->type = jbvNull;
+ return varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ break;
+ case (Oid) -1: /* raw JsonbValue */
+ *value = *(JsonbValue *) DatumGetPointer(computedValue);
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only bool, numeric and text types could be casted to supported jsonpath types")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ Numeric (*func)(Numeric, Numeric, ErrorData **);
+ Numeric res;
+ bool hasNext;
+ ErrorData *edata;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add_internal;
+ break;
+ case jpiSub:
+ func = numeric_sub_internal;
+ break;
+ case jpiMul:
+ func = numeric_mul_internal;
+ break;
+ case jpiDiv:
+ func = numeric_div_internal;
+ break;
+ case jpiMod:
+ func = numeric_mod_internal;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ edata = NULL;
+ res = func(lval->val.numeric, rval->val.numeric, &edata);
+
+ if (edata)
+ return jperMakeErrorData(edata);
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = res;
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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 *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, fmt, fmtlen, tzname, strict,
+ typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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_t 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; index++)
+ {
+ JsonbValue *v = singleton ? jb : binary ?
+ getIthJsonbValueFromContainer(jb->val.binary.data,
+ (uint32) index) :
+ &jb->val.array.elems[index];
+
+ if (v == NULL)
+ continue;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ !binary);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ ErrorData *edata = NULL;
+
+ if (JsonbType(jb) == jbvScalar)
+ jb = JsonbExtractScalar(jb->val.binary.data, &jbv);
+
+ if (jb->type == jbvNumeric)
+ {
+ /* 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 = float8_numeric_internal(val, &edata);
+ }
+ }
+ else
+ {
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ break;
+ }
+
+ if (edata)
+ {
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ThrowErrorData(edata);
+
+ FreeErrorData(edata);
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ else
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ if (tryToParseDatetime(template_str, template_len, datetime,
+ tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ {
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ JsonbValue bin;
+ JsonbValue key;
+ JsonbValue val;
+ JsonbValue idval;
+ JsonbValue obj;
+ JsonbValue keystr;
+ JsonbValue valstr;
+ JsonbValue idstr;
+ JsonbIterator *it;
+ JsonbParseState *ps = NULL;
+ int64_t id;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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 :
+#ifdef JSONPATH_JSON_C
+ (int64_t)((char *)((JsonContainer *) jb->val.binary.data)->data -
+ (char *) cxt->baseObject.jbc->data);
+#else
+ (int64_t)((char *) jb->val.binary.data -
+ (char *) cxt->baseObject.jbc);
+#endif
+ id += (int64_t) 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ 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 inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+static List*
+makePassingVars(Jsonb *jb)
+{
+ JsonbValue v;
+ JsonbIterator *it;
+ int32 r;
+ List *vars = NIL;
+
+ it = JsonbIteratorInit(&jb->root);
+
+ r = JsonbIteratorNext(&it, &v, true);
+
+ if (r != WJB_BEGIN_OBJECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("passing variable json is not a object")));
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_KEY)
+ {
+ JsonPathVariable *jpv = palloc0(sizeof(*jpv));
+
+ jpv->varName = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+
+ JsonbIteratorNext(&it, &v, true);
+
+ jpv->cb = returnDATUM;
+
+ switch(v.type)
+ {
+ case jbvBool:
+ jpv->typid = BOOLOID;
+ jpv->cb_arg = DatumGetPointer(BoolGetDatum(v.val.boolean));
+ break;
+ case jbvNull:
+ jpv->cb = returnNULL;
+ break;
+ case jbvString:
+ jpv->typid = TEXTOID;
+ jpv->cb_arg = cstring_to_text_with_len(v.val.string.val,
+ v.val.string.len);
+ break;
+ case jbvNumeric:
+ jpv->typid = NUMERICOID;
+ jpv->cb_arg = v.val.numeric;
+ break;
+ case jbvBinary:
+ jpv->typid = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "unsupported type in passing variable json");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..3856a06
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..8101ffb
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,623 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 444e575..8893878 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 171fcc8..4ba9d60 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 788f881..1ef95c3 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index ce23c2f..e08057a 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3195,5 +3195,33 @@
{ 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' },
+{ 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 4026018..74f49e5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9103,6 +9103,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index d295eae..e7ae4cc 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 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/elog.h b/src/include/utils/elog.h
index 33c6b53..42a834c 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 05e1b27..d082bdc 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/formatting.h b/src/include/utils/formatting.h
index 208cc00..6db5b3f 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 6b483a1..6ef601f 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
@@ -161,6 +204,24 @@ 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);
+
+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 27873d4..22643de 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;
@@ -219,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
@@ -236,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,
};
/*
@@ -269,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 */
@@ -277,11 +288,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,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,
@@ -363,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);
@@ -379,5 +403,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 0000000..5d16131
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..064d77e
--- /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/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index cd8da8b..6e3e3f0 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_ */
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..1c71984
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+select json '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select json '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select json '{}' @* 'strict $[0.3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{}' @* 'strict $[1.2]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{}' @* 'strict $[-2 to 3]';
+ERROR: SQL/JSON array not found
+select json '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select json '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select json '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0,1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select json '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select json '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select json '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select json '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select json '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select json '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(json '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(json '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(json '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(json '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(json '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select json '[1, "2", null]' @* '$[*] ? (@ != null)';
+ ?column?
+----------
+ 1
+ "2"
+(2 rows)
+
+select json '[1, "2", null]' @* '$[*] ? (@ == null)';
+ ?column?
+----------
+ null
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"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")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..66dea4b
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+select * from jsonpath_query(jsonb '{"a": 10}', '$');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find 'value' passed variable
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ {"a": 10}
+(1 row)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select * from jsonpath_query(jsonb '{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+(1 row)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonpath_query
+----------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonpath_query(jsonb '[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonpath_query
+----------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+select * from jsonpath_query(jsonb '[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonpath_query
+----------------
+ "1"
+(1 row)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?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?
+-----------------------------------------------
+ {"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?
+-----------------------------------------------
+ {"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()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..193fc68
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 b5e1550..b07aced 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 49329ff..c0f488e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -157,6 +157,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000..824f510
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..43f34ef
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..8a3ea42
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 708579d..17c5fb6 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -176,6 +176,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 68cf812..9998e16 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',
--
2.7.4
0003-Jsonpath-GIN-support-v20.patch (text/x-patch, 42.8K) download
From bcecf216b0e3411a37f9e7d83c682ce1b6c25e2c Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:13 +0300
Subject: [PATCH 3/4] Jsonpath GIN support
---
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 c8a2745..c11960c 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 075a54c..b2d226f 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 22643de..404ed70 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 5d16131..b3cf4c2 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 f045e08..92cf4c7 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 c073a5a..dd7b142 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 bd82fd1..1430a98 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;
--
2.7.4
0004-Jsonpath-syntax-extensions-v20.patch (text/x-patch, 46.4K) download
From 6ebc22fc8a120793e2acbcf41135e681cd57fa0a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 6 Nov 2018 16:33:14 +0300
Subject: [PATCH 4/4] Jsonpath syntax extensions
---
src/backend/utils/adt/jsonpath.c | 153 ++++++++++-
src/backend/utils/adt/jsonpath_exec.c | 394 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 +++++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 +++++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 +++++
src/test/regress/sql/json_jsonpath.sql | 47 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1227 insertions(+), 78 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11d457d..456db2e 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -136,12 +136,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
nestingLevel + argNestingLevel,
insideArraySubscript);
@@ -218,6 +221,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -305,6 +363,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -494,12 +554,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -563,6 +623,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -585,7 +693,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -688,6 +796,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -699,6 +808,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -713,7 +832,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -765,7 +885,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -869,3 +992,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index e017ac1..853c899 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -83,6 +83,8 @@ static inline JsonPathExecResult recursiveExecuteNested(JsonPathExecContext *cxt
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1686,7 +1688,116 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{
int innermostArraySize = cxt->innermostArraySize;
int i;
@@ -1771,9 +1882,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -2051,7 +2166,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2062,84 +2176,113 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- if (tryToParseDatetime(template_str, template_len, datetime,
- tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tzname)
- pfree(tzname);
- }
- else
- {
- const char *templates[] = {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- int i;
-
- for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
+ if (tryToParseDatetime(template_str, template_len,
+ datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (tryToParseDatetime(templates[i], -1, datetime,
- NULL, true, &value, &typid,
- &typmod, &tz))
+ const char *templates[] = {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ int i;
+
+ for (i = 0; i < sizeof(templates) / sizeof(*templates); i++)
{
- res = jperOk;
- break;
+ if (tryToParseDatetime(templates[i], -1, datetime,
+ NULL, true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2269,6 +2412,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 3856a06..be1d488 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -255,6 +255,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -288,9 +308,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -314,7 +334,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -327,6 +347,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -381,6 +415,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index b3cf4c2..3747985 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -84,6 +84,10 @@ typedef enum JsonPathItemType {
jpiLast, /* LAST array subscript */
jpiStartsWith, /* STARTS WITH predicate */
jpiLikeRegex, /* LIKE_REGEX predicate */
+ jpiSequence, /* sequence constructor: 'expr, ...' */
+ jpiArray, /* array constructor: '[expr, ...]' */
+ jpiObject, /* object constructor: '{ key : value, ... }' */
+ jpiObjectField, /* element of object constructor: 'key : value' */
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -138,6 +142,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -164,6 +181,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -209,6 +229,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 1c71984..86e553d 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 66dea4b..1d3215b 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 193fc68..ea29105 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 824f510..0901876 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 43f34ef..ad7a320 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 8a3ea42..653f928 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-24 20:03 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Tomas Vondra @ 2018-11-24 20:03 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
I've done another round of reviews on v20, assuming the patch is almost
ready to commit, but unfortunately I ran into a bunch of issues that
need to be resolved. None of this is a huge issue, but it's also above
the threshold of what could be tweaked by a committer IMHO.
(Which brings the question who plans to commit this. The patch does not
have a committer in the CF app, but I see both Teodor and Alexander are
listed as it's authors, so I'd expect it to be one of those. Or I might
do that, of course.)
0001
----
1) to_timestamp() does this:
do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
&tm, &fsec, &fprec, NULL);
Shouldn't it really do VARDATA_ANY() instead of VARDATA()? It's what the
function did before (well, it called text_to_cstring, but that does
VARDATA_ANY). The same thing applies to to_date(), BTW.
I also find it a bit inconvenient that we expand the fmt like this in
all do_to_timestamp() calls, although it's just to_datetime() that needs
to do it this way. I realize we can't change to_datetime() because it's
external API, but maybe we should make it construct a varlena and pass
it to do_to_timestamp().
2) We define both DCH_FF# and DCH_ff#, but we never ever use the
lower-case version. Heck, it's not mentioned even in DCH_keywords, which
does this:
...
{"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
...
{"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
...
Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
"day". Also, the comment for "ff1" is wrong, it should be "f" I guess.
And of course, there are regression tests for FF# variants, but not the
lower-case ones.
3) Of course, these new formatting patterns should be added to SGML
docs, for example to "Template Patterns for Date/Time Formatting" in
func.sgml (and maybe to other places, I haven't looked for them very
thoroughly).
4) The last block in DCH_from_char() does this
while (*s == ' ')
s++;
but I suppose it should use isspace() just like the code immediately
before it.
5) I might be missing something, but why is there the "D" at the end of
the return flags from DCH_from_char?
/* Return flags for DCH_from_char() */
#define DCH_DATED 0x01
#define DCH_TIMED 0x02
#define DCH_ZONED 0x04
0002
----
1) There are some unnecessary changes to to_datetime signature (date_txt
renamed to vs. datetxt), which is mostly minor but unnecessary churn.
2) There are some extra changes to to_datetime (extra parameters, etc.).
I wonder why those are not included in 0001, as part of the supporting
datetime infrastructure.
3) I'm not sure whether the _safe functions are a good or bad idea, but
at the very least the comments should be updated to explain what it does
(as the API has changed, obviously).
4) the json.c changes are under-documented, e.g. there are no comments
for lex_peek_value, JsonEncodeDateTime doesn't say what tzp is for and
whether it needs to be specified all the time, half of the functions at
the end don't have comments (some of them are really simple, but then
other simple functions do have comments).
I don't know what the right balance here is (I'm certainly not asking
for bogus comments just to have comments) and I agree that the existing
code is not exactly abundant in comments. But perhaps having at least
some would be nice.
The same thing applies to jsonpath.c and jsonpath_exec.c, I think. There
are pretty much no comments whatsoever (at least at the function level,
explaining what the functions do). It would be good to have a file-level
comment too, explaining how jsonpath works, probably.
5) I see uniqueifyJsonbObject now does this:
if (!object->val.object.uniquified)
return;
That seems somewhat strange, considering the function says it'll
uniqueify objects, but then exits when noticing the passed object is not
uniquified?
6) Why do we make make_result inline? (numeric.c) If this needs to be
marked with "inline" then perhaps all the _internal functions should be
marked like that too? I have my doubts about the need for this.
7) The other places add _safe to functions that don't throw errors
directly and instead update edata. Why are set_var_from_str, div_var,
mod_var excluded from this convention?
8) I wonder if the changes in numeric can have negative impact on
performance. Has anyone done any performance tests of this part?
0003
----
1) json_gin.c should probably add comments briefly explaining what
JsonPathNode, GinEntries, ExtractedPathEntry, ExtractedJsonPath and
JsonPathExtractionContext are for.
2) I find it a bit suspicious that there are no asserts in json_gin.c
(well, there are 3 in the existing code, but nothing in the new code,
and the patch essentially doubles the amount of code here).
No comments for 0004 at this point.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-11-28 20:26 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2018-11-28 20:26 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi!
On Sat, Nov 24, 2018 at 11:03 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> I've done another round of reviews on v20, assuming the patch is almost
> ready to commit, but unfortunately I ran into a bunch of issues that
> need to be resolved. None of this is a huge issue, but it's also above
> the threshold of what could be tweaked by a committer IMHO.
>
> (Which brings the question who plans to commit this. The patch does not
> have a committer in the CF app, but I see both Teodor and Alexander are
> listed as it's authors, so I'd expect it to be one of those. Or I might
> do that, of course.)
Thanks a lot for your efforts on this patch! I was planning to work
on this patch during this commitfest (and probably commit). But I
didn't manage to do this due to family circumstances (I got my baby
born and hardly could allocate time to work in November). But I'm
still planning to commit this patch. So, I'm going to set myself as
committer in commitfest application.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-12-01 13:05 Dmitry Dolgov <9erthalion6@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 0 replies; 198+ messages in thread
From: Dmitry Dolgov @ 2018-12-01 13:05 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
> On Wed, Nov 28, 2018 at 9:26 PM Alexander Korotkov <a.korotkov@postgrespro.ru> wrote:
>
> On Sat, Nov 24, 2018 at 11:03 PM Tomas Vondra
> <tomas.vondra@2ndquadrant.com> wrote:
> > I've done another round of reviews on v20, assuming the patch is almost
> > ready to commit, but unfortunately I ran into a bunch of issues that
> > need to be resolved. None of this is a huge issue, but it's also above
> > the threshold of what could be tweaked by a committer IMHO.
> >
> > (Which brings the question who plans to commit this. The patch does not
> > have a committer in the CF app, but I see both Teodor and Alexander are
> > listed as it's authors, so I'd expect it to be one of those. Or I might
> > do that, of course.)
>
> Thanks a lot for your efforts on this patch! I was planning to work
> on this patch during this commitfest (and probably commit). But I
> didn't manage to do this due to family circumstances (I got my baby
> born and hardly could allocate time to work in November). But I'm
> still planning to commit this patch. So, I'm going to set myself as
> committer in commitfest application.
Great news, thank you! Just for the information, cfbot doesn't like the patch
on windows:
src/backend/utils/adt/jsonpath_exec.c(1539): error C2146: syntax error
: missing ')' before identifier 'id'
[C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1539): error C2081: 'int32_t' :
name in formal parameter list illegal
[C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1539): error C2061: syntax error
: identifier 'id' [C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1539): error C2059: syntax error
: ';' [C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1539): error C2059: syntax error
: ')' [C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1540): error C2449: found '{' at
file scope (missing function header?)
[C:\projects\postgresql\postgres.vcxproj]
src/backend/utils/adt/jsonpath_exec.c(1548): error C2059: syntax error
: '}' [C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1539):
error C2146: syntax error : missing ')' before identifier 'id'
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1539):
error C2081: 'int32_t' : name in formal parameter list illegal
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1539):
error C2061: syntax error : identifier 'id'
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1539):
error C2059: syntax error : ';'
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1539):
error C2059: syntax error : ')'
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1540):
error C2449: found '{' at file scope (missing function header?)
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
c:\projects\postgresql\src\backend\utils\adt\jsonpath_exec.c(1548):
error C2059: syntax error : '}'
(src/backend/utils/adt/jsonpath_json.c)
[C:\projects\postgresql\postgres.vcxproj]
10 Warning(s)
14 Error(s)
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2018-12-03 23:25 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2018-12-03 23:25 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 21-st version of the patches rebased onto the current master.
Added new patch 0004 with documentation written by Liudmila Mantrova, and
optional patch 0003 that was separated from 0002.
On 24.11.2018 23:03, Tomas Vondra wrote:
> Hi,
>
> I've done another round of reviews on v20, assuming the patch is almost
> ready to commit, but unfortunately I ran into a bunch of issues that
> need to be resolved. None of this is a huge issue, but it's also above
> the threshold of what could be tweaked by a committer IMHO.
Thank your for your review.
> (Which brings the question who plans to commit this. The patch does not
> have a committer in the CF app, but I see both Teodor and Alexander are
> listed as it's authors, so I'd expect it to be one of those. Or I might
> do that, of course.)
> 0001
> ----
>
> 1) to_timestamp() does this:
>
> do_to_timestamp(date_txt, VARDATA(fmt), VARSIZE_ANY_EXHDR(fmt), false,
> &tm, &fsec, &fprec, NULL);
>
> Shouldn't it really do VARDATA_ANY() instead of VARDATA()? It's what the
> function did before (well, it called text_to_cstring, but that does
> VARDATA_ANY). The same thing applies to to_date(), BTW.
>
> I also find it a bit inconvenient that we expand the fmt like this in
> all do_to_timestamp() calls, although it's just to_datetime() that needs
> to do it this way. I realize we can't change to_datetime() because it's
> external API, but maybe we should make it construct a varlena and pass
> it to do_to_timestamp().
Of course, there must be VARDATA_ANY() instead of of VARDATA(). But I decided
to construct varlena and pass it to do_to_timestamp() and to to_datetime().
> 2) We define both DCH_FF# and DCH_ff#, but we never ever use the
> lower-case version. Heck, it's not mentioned even in DCH_keywords, which
> does this:
>
> ...
> {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> ...
> {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> ...
>
> Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
> "day".
Yes, "ff#" are mapped to DCH_FF# like "mi" is mapped DCH_MI.
"Day", "day" are not mapped to DCH_DAY because they determine letter case in the
output, but "ff1" and "FF#" output contains only digits.
> Also, the comment for "ff1" is wrong, it should be "f" I guess.
Fixed.
> And of course, there are regression tests for FF# variants, but not the
> lower-case ones.
Tests were added
> 3) Of course, these new formatting patterns should be added to SGML
> docs, for example to "Template Patterns for Date/Time Formatting" in
> func.sgml (and maybe to other places, I haven't looked for them very
> thoroughly).
Fixed.
> 4) The last block in DCH_from_char() does this
>
> while (*s == ' ')
> s++;
>
> but I suppose it should use isspace() just like the code immediately
> before it.
Fixed.
> 5) I might be missing something, but why is there the "D" at the end of
> the return flags from DCH_from_char?
>
> /* Return flags for DCH_from_char() */
> #define DCH_DATED 0x01
> #define DCH_TIMED 0x02
> #define DCH_ZONED 0x04
These terms "dated", "timed", and "zoned" are taken from the standard:
9.39.10) If <JSON datetime template> JDT is specified, ... :
a) If JDT contains <datetime template year>, ... then JDT is dated.
b) If JDT contains <datetime template 12-hour>, ... then JDT is timed.
c) If JDT contains <datetime template time zone hour>, ... then JDT is zoned.
> 0002
> ----
>
> 1) There are some unnecessary changes to to_datetime signature (date_txt
> renamed to vs. datetxt), which is mostly minor but unnecessary churn.
>
> 2) There are some extra changes to to_datetime (extra parameters, etc.).
> I wonder why those are not included in 0001, as part of the supporting
> datetime infrastructure.
Extra changes to to_datetime() were moved into patch 0001.
> 3) I'm not sure whether the _safe functions are a good or bad idea, but
> at the very least the comments should be updated to explain what it does
> (as the API has changed, obviously).
This functions were introduced for elimination of PG_TRY/PG_CATCH in jsonpath
code. But this error handling approach has a lot of issues (see below), so I
decided separate again them into optional patch 0004.
> 4) the json.c changes are under-documented, e.g. there are no comments
> for lex_peek_value, JsonEncodeDateTime doesn't say what tzp is for and
> whether it needs to be specified all the time, half of the functions at
> the end don't have comments (some of them are really simple, but then
> other simple functions do have comments).
>
> I don't know what the right balance here is (I'm certainly not asking
> for bogus comments just to have comments) and I agree that the existing
> code is not exactly abundant in comments. But perhaps having at least
> some would be nice.
Some new comments were added to json.c.
> The same thing applies to jsonpath.c and jsonpath_exec.c, I think. There
> are pretty much no comments whatsoever (at least at the function level,
> explaining what the functions do). It would be good to have a file-level
> comment too, explaining how jsonpath works, probably.
I hope to add detailed comments for jsonpath in the next version of the
patches.
> 5) I see uniqueifyJsonbObject now does this:
>
> if (!object->val.object.uniquified)
> return;
>
> That seems somewhat strange, considering the function says it'll
> uniqueify objects, but then exits when noticing the passed object is not
> uniquified?
'uniquified' field was rename to 'uniquify'.
> 6) Why do we make make_result inline? (numeric.c) If this needs to be
> marked with "inline" then perhaps all the _internal functions should be
> marked like that too? I have my doubts about the need for this.
>
>
> 7) The other places add _safe to functions that don't throw errors
> directly and instead update edata. Why are set_var_from_str, div_var,
> mod_var excluded from this convention?
I added "_safe" postfix only to functions having the both variants: safe (error
info is placed into *edata) and unsafe (errors are always thrown).
One-line make_result() is called from many unsafe places, so I decided to leave
it as it was. It can also be defined as a simple macro.
New "_internal" functions are called from jsonpath_exec.c, so they can't be
"static inline", but "extern inline" seems to be non-portable.
> 8) I wonder if the changes in numeric can have negative impact on
> performance. Has anyone done any performance tests of this part?
I've tried to measure this impact by evaluation of expression with dozens of '+'
or even by adding a loop into numeric_add(), but failed to get any measurable
difference.
> 0003
> ----
>
> 1) jsonb_gin.c should probably add comments briefly explaining what
> JsonPathNode, GinEntries, ExtractedPathEntry, ExtractedJsonPath and
> JsonPathExtractionContext are for.
A lot of comments were added. Also major refactoring of jsonb_gin.c was
done.
> 2) I find it a bit suspicious that there are no asserts in json_gin.c
> (well, there are 3 in the existing code, but nothing in the new code,
> and the patch essentially doubles the amount of code here).
I have managed to find only 4 similar places suitable for asserts.
> No comments for 0004 at this point.
>
> regards
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (6 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v21.patch (text/x-patch, 35.2K) download
From 9b5fab17f887236b092a38ef12b5500fe8b35b14 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:10 +0300
Subject: [PATCH 01/13] Preliminary datetime infrastructure
---
doc/src/sgml/func.sgml | 24 ++
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 438 ++++++++++++++++++++++++++++--
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 86 ++++++
src/test/regress/expected/timestamp.out | 29 ++
src/test/regress/expected/timestamptz.out | 28 ++
src/test/regress/sql/horology.sql | 14 +
src/test/regress/sql/timestamp.sql | 16 ++
src/test/regress/sql/timestamptz.sql | 16 ++
13 files changed, 639 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b3336ea..ae2ace3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5990,6 +5990,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry>microsecond (000000-999999)</entry>
</row>
<row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
+ <row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
</row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index cb6b5e55..7d5c8ac 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 2923afe..9ce15fe 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,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,
@@ -645,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,
@@ -745,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},
@@ -794,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},
@@ -895,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
@@ -962,6 +1000,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1019,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1037,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,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 +
@@ -3007,13 +3072,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;
@@ -3149,8 +3216,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3164,6 +3241,14 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
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:
@@ -3374,6 +3459,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3496,112 @@ 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_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;
+ 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)
@@ -3682,8 +3890,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3910,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);
}
@@ -3718,7 +3931,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3953,175 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
+/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4129,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' 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4191,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4379,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index b377c38..d85dc21 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 eb6d2a1..10cc822 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 de9e9ad..165f0e7 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 a9f5548..35ab1ba 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..fa27d74 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 4a2fabd..a477545 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,35 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+-- 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
+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 8a4c719..bbdf8c0 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,34 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+-- 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
+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 e356dd5..ef34323 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 b7957cb..86bf5d3 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,21 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
+-- 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');
+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 c3bd46c..d007957 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,22 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
+-- 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');
+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";
--
2.7.4
0002-Jsonpath-engine-and-operators-v21.patch (text/x-patch, 319.9K) download
From b2daedf898033db31106503b6fb30911608bdae7 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:10 +0300
Subject: [PATCH 02/13] Jsonpath engine and operators
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 20 +-
src/backend/utils/adt/json.c | 890 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 871 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2864 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 495 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 623 ++++++
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 5 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/.gitignore | 1 +
src/include/utils/jsonapi.h | 64 +-
src/include/utils/jsonb.h | 39 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 106 +
src/include/utils/jsonpath_scanner.h | 30 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 +++++++++++++++
src/test/regress/expected/jsonpath.out | 800 +++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
35 files changed, 11663 insertions(+), 48 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_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
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 25eb043..ca8a19b 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
@@ -308,6 +316,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 df7e01f..fffc791 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 0000000..7fab054
--- /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 20eead1..8db7f98 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 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 \
orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \
@@ -32,6 +33,23 @@ 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_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index f47a498..4850e09 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 =
{
@@ -127,6 +130,27 @@ lex_peek(JsonLexContext *lex)
}
/*
+ * lex_peek_value
+ *
+ * get the current look_ahead de-escaped lexeme.
+*/
+static inline char *
+lex_peek_value(JsonLexContext *lex)
+{
+ if (lex->token_type == JSON_TOKEN_STRING)
+ return lex->strval ? pstrdup(lex->strval->data) : NULL;
+ else
+ {
+ int len = lex->token_terminator - lex->token_start;
+ char *tokstr = palloc(len + 1);
+
+ memcpy(tokstr, lex->token_start, len);
+ tokstr[len] = '\0';
+ return tokstr;
+ }
+}
+
+/*
* lex_accept
*
* accept the look_ahead token and move the lexer to the next token if the
@@ -141,22 +165,8 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
if (lex->token_type == token)
{
if (lexeme != NULL)
- {
- if (lex->token_type == JSON_TOKEN_STRING)
- {
- if (lex->strval != NULL)
- *lexeme = pstrdup(lex->strval->data);
- }
- else
- {
- int len = (lex->token_terminator - lex->token_start);
- char *tokstr = palloc(len + 1);
+ *lexeme = lex_peek_value(lex);
- memcpy(tokstr, lex->token_start, len);
- tokstr[len] = '\0';
- *lexeme = tokstr;
- }
- }
json_lex(lex);
return true;
}
@@ -1506,7 +1516,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 +1524,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 +1532,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1560,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1641,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),
@@ -2553,3 +2583,821 @@ json_typeof(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(type));
}
+
+/*
+ * Initialize a JsonContainer from a json text, its type and size.
+ * 'type' can be JB_FOBJECT, JB_FARRAY, (JB_FARRAY | JB_FSCALAR).
+ * 'size' is a number of elements/pairs in array/object, or -1 if unknown.
+ */
+static void
+jsonInitContainer(JsonContainerData *jc, char *json, int len, int type,
+ int size)
+{
+ if (size < 0 || size > JB_CMASK)
+ size = JB_CMASK; /* unknown size */
+
+ jc->data = json;
+ jc->len = len;
+ jc->header = type | size;
+}
+
+/*
+ * Initialize a JsonContainer from a text datum.
+ */
+static void
+jsonInit(JsonContainerData *jc, Datum value)
+{
+ text *json = DatumGetTextP(value);
+ JsonLexContext *lex = makeJsonLexContext(json, false);
+ JsonTokenType tok;
+ int type;
+ int size = -1;
+
+ /* Lex exactly one token from the input and check its type. */
+ json_lex(lex);
+ tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ type = JB_FOBJECT;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_OBJECT_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ type = JB_FARRAY;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_ARRAY_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ type = JB_FARRAY | JB_FSCALAR;
+ size = 1;
+ break;
+ default:
+ elog(ERROR, "unexpected json token: %d", tok);
+ type = jbvNull;
+ break;
+ }
+
+ pfree(lex);
+
+ jsonInitContainer(jc, VARDATA(json), VARSIZE(json) - VARHDRSZ, type, size);
+}
+
+/*
+ * Wrap JSON text into a palloc()'d Json structure.
+ */
+Json *
+JsonCreate(text *json)
+{
+ Json *res = palloc0(sizeof(*res));
+
+ jsonInit((JsonContainerData *) &res->root, PointerGetDatum(json));
+
+ return res;
+}
+
+/*
+ * Fill JsonbValue from the current iterator token.
+ * Returns true if recursion into nested object or array is needed (in this case
+ * child iterator is created and put into *pit).
+ */
+static bool
+jsonFillValue(JsonIterator **pit, JsonbValue *res, bool skipNested,
+ JsontIterState nextState)
+{
+ JsonIterator *it = *pit;
+ JsonLexContext *lex = it->lex;
+ JsonTokenType tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_NULL:
+ res->type = jbvNull;
+ break;
+
+ case JSON_TOKEN_TRUE:
+ res->type = jbvBool;
+ res->val.boolean = true;
+ break;
+
+ case JSON_TOKEN_FALSE:
+ res->type = jbvBool;
+ res->val.boolean = false;
+ break;
+
+ case JSON_TOKEN_STRING:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvString;
+ res->val.string.val = token;
+ res->val.string.len = strlen(token);
+ break;
+ }
+
+ case JSON_TOKEN_NUMBER:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvNumeric;
+ res->val.numeric = DatumGetNumeric(DirectFunctionCall3(
+ numeric_in, CStringGetDatum(token), 0, -1));
+ break;
+ }
+
+ case JSON_TOKEN_OBJECT_START:
+ case JSON_TOKEN_ARRAY_START:
+ {
+ JsonContainerData *cont = palloc(sizeof(*cont));
+ char *token_start = lex->token_start;
+ int len;
+
+ if (skipNested)
+ {
+ /* find the end of a container for its length calculation */
+ if (tok == JSON_TOKEN_OBJECT_START)
+ parse_object(lex, &nullSemAction);
+ else
+ parse_array(lex, &nullSemAction);
+
+ len = lex->token_start - token_start;
+ }
+ else
+ len = lex->input_length - (lex->token_start - lex->input);
+
+ jsonInitContainer(cont,
+ token_start, len,
+ tok == JSON_TOKEN_OBJECT_START ?
+ JB_FOBJECT : JB_FARRAY,
+ -1);
+
+ res->type = jbvBinary;
+ res->val.binary.data = (JsonbContainer *) cont;
+ res->val.binary.len = len;
+
+ if (skipNested)
+ return false;
+
+ /* recurse into container */
+ it->state = nextState;
+ *pit = JsonIteratorInitFromLex(cont, lex, *pit);
+ return true;
+ }
+
+ default:
+ report_parse_error(JSON_PARSE_VALUE, lex);
+ }
+
+ lex_accept(lex, tok, NULL);
+
+ return false;
+}
+
+/*
+ * Free the topmost entry in the stack of JsonIterators.
+ */
+static inline JsonIterator *
+JsonIteratorFreeAndGetParent(JsonIterator *it)
+{
+ JsonIterator *parent = it->parent;
+
+ pfree(it);
+
+ return parent;
+}
+
+/*
+ * Free the entire stack of JsonIterators.
+ */
+void
+JsonIteratorFree(JsonIterator *it)
+{
+ while (it)
+ it = JsonIteratorFreeAndGetParent(it);
+}
+
+/*
+ * Get next JsonbValue while iterating through JsonContainer.
+ *
+ * For more details, see JsonbIteratorNext().
+ */
+JsonbIteratorToken
+JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested)
+{
+ JsonIterator *it;
+
+ if (*pit == NULL)
+ return WJB_DONE;
+
+recurse:
+ it = *pit;
+
+ /* parse by recursive descent */
+ switch (it->state)
+ {
+ case JTI_ARRAY_START:
+ val->type = jbvArray;
+ val->val.array.nElems = it->isScalar ? 1 : -1;
+ val->val.array.rawScalar = it->isScalar;
+ val->val.array.elems = NULL;
+ it->state = it->isScalar ? JTI_ARRAY_ELEM_SCALAR : JTI_ARRAY_ELEM;
+ return WJB_BEGIN_ARRAY;
+
+ case JTI_ARRAY_ELEM_SCALAR:
+ {
+ (void) jsonFillValue(pit, val, skipNested, JTI_ARRAY_END);
+ it->state = JTI_ARRAY_END;
+ return WJB_ELEM;
+ }
+
+ case JTI_ARRAY_END:
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_ARRAY;
+
+ case JTI_ARRAY_ELEM:
+ if (lex_accept(it->lex, JSON_TOKEN_ARRAY_END, NULL))
+ {
+ it->state = JTI_ARRAY_END;
+ goto recurse;
+ }
+
+ if (jsonFillValue(pit, val, skipNested, JTI_ARRAY_ELEM_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_ARRAY_ELEM_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_ARRAY_END)
+ report_parse_error(JSON_PARSE_ARRAY_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_ARRAY_ELEM_AFTER)
+ {
+ it->state = JTI_ARRAY_ELEM;
+ goto recurse;
+ }
+
+ return WJB_ELEM;
+
+ case JTI_OBJECT_START:
+ val->type = jbvObject;
+ val->val.object.nPairs = -1;
+ val->val.object.pairs = NULL;
+ val->val.object.uniquify = false;
+ it->state = JTI_OBJECT_KEY;
+ return WJB_BEGIN_OBJECT;
+
+ case JTI_OBJECT_KEY:
+ if (lex_accept(it->lex, JSON_TOKEN_OBJECT_END, NULL))
+ {
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_OBJECT;
+ }
+
+ if (lex_peek(it->lex) != JSON_TOKEN_STRING)
+ report_parse_error(JSON_PARSE_OBJECT_START, it->lex);
+
+ (void) jsonFillValue(pit, val, true, JTI_OBJECT_VALUE);
+
+ if (!lex_accept(it->lex, JSON_TOKEN_COLON, NULL))
+ report_parse_error(JSON_PARSE_OBJECT_LABEL, it->lex);
+
+ it->state = JTI_OBJECT_VALUE;
+ return WJB_KEY;
+
+ case JTI_OBJECT_VALUE:
+ if (jsonFillValue(pit, val, skipNested, JTI_OBJECT_VALUE_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_OBJECT_VALUE_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_OBJECT_END)
+ report_parse_error(JSON_PARSE_OBJECT_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_OBJECT_VALUE_AFTER)
+ {
+ it->state = JTI_OBJECT_KEY;
+ goto recurse;
+ }
+
+ it->state = JTI_OBJECT_KEY;
+ return WJB_VALUE;
+
+ default:
+ break;
+ }
+
+ return WJB_DONE;
+}
+
+/* Initialize JsonIterator from json lexer which onto the first token. */
+static JsonIterator *
+JsonIteratorInitFromLex(JsonContainer *jc, JsonLexContext *lex,
+ JsonIterator *parent)
+{
+ JsonIterator *it = palloc(sizeof(JsonIterator));
+ JsonTokenType tok;
+
+ it->container = jc;
+ it->parent = parent;
+ it->lex = lex;
+
+ tok = lex_peek(it->lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ it->isScalar = false;
+ it->state = JTI_OBJECT_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ it->isScalar = false;
+ it->state = JTI_ARRAY_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ it->isScalar = true;
+ it->state = JTI_ARRAY_START;
+ break;
+ default:
+ report_parse_error(JSON_PARSE_VALUE, it->lex);
+ }
+
+ return it;
+}
+
+/*
+ * Given a JsonContainer, expand to JsonIterator to iterate over items
+ * fully expanded to in-memory representation for manipulation.
+ *
+ * See JsonbIteratorNext() for notes on memory management.
+ */
+JsonIterator *
+JsonIteratorInit(JsonContainer *jc)
+{
+ JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, true);
+ json_lex(lex);
+ return JsonIteratorInitFromLex(jc, lex, NULL);
+}
+
+/*
+ * Serialize a single JsonbValue into text buffer.
+ */
+static void
+JsonEncodeJsonbValue(StringInfo buf, JsonbValue *jbv)
+{
+ check_stack_depth();
+
+ switch (jbv->type)
+ {
+ case jbvNull:
+ appendBinaryStringInfo(buf, "null", 4);
+ break;
+
+ case jbvBool:
+ if (jbv->val.boolean)
+ appendBinaryStringInfo(buf, "true", 4);
+ else
+ appendBinaryStringInfo(buf, "false", 5);
+ break;
+
+ case jbvNumeric:
+ 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,
+ &jbv->val.datetime.tz);
+
+ 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 "uniquify" flag in objects.
+ */
+JsonbValue *
+pushJsonValue(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *jbval)
+{
+ JsonIterator *it;
+ JsonbValue *res = NULL;
+ JsonbValue v;
+ JsonbIteratorToken tok;
+
+ if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) ||
+ jbval->type != jbvBinary)
+ {
+ /* drop through */
+ res = pushJsonbValueScalar(pstate, seq, jbval);
+
+ /* reset "uniquify" flag of objects */
+ if (seq == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+
+ return res;
+ }
+
+ /* unpack the binary and add each piece to the pstate */
+ it = JsonIteratorInit((JsonContainer *) jbval->val.binary.data);
+ while ((tok = JsonIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ res = pushJsonbValueScalar(pstate, tok,
+ tok < WJB_BEGIN_ARRAY ? &v : NULL);
+
+ /* reset "uniquify" flag of objects */
+ if (tok == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+ }
+
+ return res;
+}
+
+/*
+ * Extract scalar JsonbValue from a scalar json.
+ */
+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.c b/src/backend/utils/adt/jsonb.c
index 0ae9d7b..00a7f3a 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 713631b..015358a 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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.uniquify = true;
break;
case WJB_KEY:
Assert(scalarVal->type == jbvString);
@@ -822,6 +822,7 @@ recurse:
/* Set v to object on first object call */
val->type = jbvObject;
val->val.object.nPairs = (*it)->nElems;
+ val->val.object.uniquify = true;
/*
* v->val.object.pairs is not actually set, because we aren't
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,6 +1840,9 @@ uniqueifyJsonbObject(JsonbValue *object)
Assert(object->type == jbvObject);
+ if (!object->val.object.uniquify)
+ return;
+
if (object->val.object.nPairs > 1)
qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
lengthCompareJsonbPair, &hasNonUniq);
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..11d457d
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..e6646c2
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2864 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "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/formatting.h"
+#include "utils/json.h"
+#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
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+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);
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ 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();
+ ErrorData *edata;
+
+ if (ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ MemoryContextSwitchTo(mcxt);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, 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, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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 :
+#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);
+#endif
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ 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 inline 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);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "invalid jsonb value type");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..3856a06
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..8101ffb
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,623 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len) {
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 171fcc8..4ba9d60 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 788f881..1ef95c3 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index ce23c2f..e08057a 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3195,5 +3195,33 @@
{ 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' },
+{ 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 034a41e..b1d3dd8 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9112,6 +9112,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index d295eae..e7ae4cc 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 8551237..ff1ecb2 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a..e0705e1 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/jsonapi.h b/src/include/utils/jsonapi.h
index 6b483a1..a6148ba 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
@@ -161,6 +204,25 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
+
+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 27873d4..76e98eb 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;
@@ -219,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
@@ -236,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,
};
/*
@@ -270,6 +279,8 @@ struct JsonbValue
{
int nPairs; /* 1 pair, 2 elements */
JsonbPair *pairs;
+ bool uniquify; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
} object; /* Associative container type */
struct
@@ -277,11 +288,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,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,
@@ -363,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);
@@ -379,5 +403,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 0000000..5d16131
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..064d77e
--- /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/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..942bf6b
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+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 $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"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")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..f93c930
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+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)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?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?
+-----------------------------------------------
+ {"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?
+-----------------------------------------------
+ {"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()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..193fc68
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5..177e031 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0c10c71..fa6a1d2 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -156,6 +156,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000..824f510
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..43f34ef
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..8a3ea42
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 2921d19..f76ca45 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 68cf812..9998e16 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',
--
2.7.4
0003-Remove-PG_TRY-in-jsonpath-arithmetics-v21.patch (text/x-patch, 28.0K) download
From a8f59a6a0d21bbcd71db5123c68c355a304d7d6e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:10 +0300
Subject: [PATCH 03/13] Remove PG_TRY in jsonpath arithmetics
---
src/backend/utils/adt/float.c | 48 +++---
src/backend/utils/adt/jsonpath_exec.c | 108 +++++-------
src/backend/utils/adt/numeric.c | 302 ++++++++++++++++++++++------------
src/include/utils/elog.h | 19 +++
src/include/utils/float.h | 7 +-
src/include/utils/numeric.h | 9 +
6 files changed, 305 insertions(+), 188 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index cf9327f..ffe0813 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 e6646c2..b79e01a 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);
}
@@ -2032,53 +2014,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 444e575..3282b8c 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,17 @@ 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.
+ *
+ * If edata is non-NULL then when numeric-related error occurs error info
+ * should be placed into *edata (not thrown) and NULL is returned.
*/
static Numeric
-make_result(const NumericVar *var)
+make_result_safe(const NumericVar *var, ErrorData **edata)
{
Numeric result;
NumericDigit *digits = var->digits;
@@ -6166,14 +6240,27 @@ 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;
}
+/*
+ * make_result() -
+ *
+ * Same as make_result_safe(), but numeric-related errors are always thrown.
+ */
+static inline Numeric
+make_result(const NumericVar *var)
+{
+ return make_result_safe(var, NULL);
+}
/*
* apply_typmod() -
@@ -7051,7 +7138,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 +7163,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 +7789,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 +7802,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 +8458,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 33c6b53..42a834c 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 05e1b27..d082bdc 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 cd8da8b..6e3e3f0 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_ */
--
2.7.4
0004-Jsonpath-docs-v21.patch (text/x-patch, 40.1K) download
From 58a227b67fc9ae713cb97679bb1756eb8afd9039 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:10 +0300
Subject: [PATCH 04/13] Jsonpath docs
---
doc/src/sgml/biblio.sgml | 11 +
doc/src/sgml/func.sgml | 693 ++++++++++++++++++++++++++++++++++++++++++++++-
doc/src/sgml/json.sgml | 250 ++++++++++++++++-
3 files changed, 945 insertions(+), 9 deletions(-)
diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 4953024..f06305d 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index ae2ace3..0d844c3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11309,26 +11309,661 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Enclosing the path specification into square brackets
+ <literal>[]</literal> automatically wraps the path evaluation
+ result into an array.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>Array of objects containing two members ("key" and "value" of the SQL/JSON item)</entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20"}, {"key": "y", "value": 32}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-extra-op-table">
+ <title>Extended <type>jsonpath</type> Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>min()</literal></entry>
+ <entry>Minimum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.min()</literal></entry>
+ <entry><literal>0</literal></entry>
+ </row>
+ <row>
+ <entry><literal>max()</literal></entry>
+ <entry>Maximum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.max()</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>map()</literal></entry>
+ <entry>Calculate an expression by applying a given function
+ to each element of the json array
+ </entry>
+ <entry><literal>[1, 2, 0]</literal></entry>
+ <entry><literal>$.map(@ * 2)</literal></entry>
+ <entry><literal>[2, 4, 0]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>reduce()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[3, 5, 9]</literal></entry>
+ <entry><literal>$.reduce($1 + $2)</literal></entry>
+ <entry><literal>17</literal></entry>
+ </row>
+ <row>
+ <entry><literal>fold()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[2, 3, 4]</literal></entry>
+ <entry><literal>$.fold($1 * $2, 1)</literal></entry>
+ <entry><literal>24</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldl()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from left to right
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldl([$1, $2], [])</literal></entry>
+ <entry><literal>[[[[], 1], 2], 3]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldr()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from right to left
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldr([$2, $1], [])</literal></entry>
+ <entry><literal>[[[[], 3], 2], 1]</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11338,6 +11973,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11346,6 +11982,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11353,6 +11990,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11360,6 +11998,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11367,17 +12006,55 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@#</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for the specified JSON value. If there is more than one item, they will be wrapped into an array.</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @# '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>[3, 4, 5]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Check whether JSON path returns any item for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12143,6 +12820,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12197,6 +12875,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa..2ba7520 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -536,6 +549,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
--
2.7.4
0005-Jsonpath-GIN-support-v21.patch (text/x-patch, 47.0K) download
From 75a9fca86b3eb1167c568e355e497b424ffdb435 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:11 +0300
Subject: [PATCH 05/13] Jsonpath GIN support
---
doc/src/sgml/gin.sgml | 4 +
src/backend/utils/adt/jsonb_gin.c | 903 ++++++++++++++++++++++++++++---
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 +++
8 files changed, 1384 insertions(+), 76 deletions(-)
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index c8a2745..90da12a 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,120 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+/* Buffer for GIN entries */
+typedef struct GinEntries
+{
+ Datum *buf;
+ int count;
+ int allocated;
+} GinEntries;
+
+typedef enum GinJsonPathNodeType
+{
+ GIN_JSP_OR,
+ GIN_JSP_AND,
+ GIN_JSP_ENTRY
+} GinJsonPathNodeType;
+
+typedef struct GinJsonPathNode GinJsonPathNode;
+
+/* Node in jsonpath expression tree */
+struct GinJsonPathNode
+{
+ GinJsonPathNodeType type;
+ union
+ {
+ int nargs; /* valid for OR and AND nodes */
+ int entryIndex; /* index in GinEntries array, valid for
+ * ENTRY nodes after entries output */
+ Datum entryDatum; /* path hash or key name/scalar, valid
+ * for ENTRY nodes before entries output */
+ } val;
+ GinJsonPathNode *args[FLEXIBLE_ARRAY_MEMBER]; /* valid for OR and AND nodes */
+};
+
+/*
+ * Single entry in the extracted json path (used for jsonb_ops only).
+ * Path entry can be one of:
+ * .key (key name is stored in 'entry' field)
+ * .*
+ * .**
+ * [index]
+ * [*]
+ * Entry type is stored in 'type' field.
+ */
+typedef struct GinJsonPathEntry
+{
+ struct GinJsonPathEntry *parent;
+ Datum entry; /* key name or NULL */
+ JsonPathItemType type;
+} GinJsonPathEntry;
+
+/* GIN representation of the extracted json path */
+typedef union GinJsonPath
+{
+ GinJsonPathEntry *entries; /* list of path entries (jsonb_ops) */
+ uint32 hash; /* hash of the path (jsonb_path_ops) */
+} GinJsonPath;
+
+typedef struct GinJsonPathContext GinJsonPathContext;
+
+/* Add entry to the extracted json path */
+typedef bool (*GinAddPathEntryFunc)(GinJsonPath *path, JsonPathItem *jsp);
+typedef List *(*GinExtractPathNodesFunc)(GinJsonPathContext *cxt,
+ GinJsonPath path, JsonbValue *scalar,
+ List *nodes);
+
+/* Context for jsonpath entries extraction */
+struct GinJsonPathContext
+{
+ GinAddPathEntryFunc add_path_entry;
+ GinExtractPathNodesFunc extract_path_nodes;
+ bool lax;
+};
+
static Datum make_text_key(char flag, const char *str, int len);
static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key);
+static GinJsonPathNode *extract_jsp_bool_expr(GinJsonPathContext *cxt,
+ GinJsonPath path, JsonPathItem *jsp, bool not);
+
+
+/* Init GIN entry buffer. */
+static void
+init_entries(GinEntries *entries, int preallocated)
+{
+ entries->allocated = preallocated;
+ entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL;
+ entries->count = 0;
+}
+
+/* Add GIN entry to the buffer. */
+static int
+add_entry(GinEntries *entries, Datum entry)
+{
+ int id = entries->count;
+
+ if (entries->count >= entries->allocated)
+ {
+ if (entries->allocated)
+ {
+ entries->allocated *= 2;
+ entries->buf = repalloc(entries->buf,
+ sizeof(Datum) * entries->allocated);
+ }
+ else
+ {
+ entries->allocated = 8;
+ entries->buf = palloc(sizeof(Datum) * entries->allocated);
+ }
+ }
+
+ entries->buf[entries->count++] = entry;
+
+ return id;
+}
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -68,12 +181,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 +195,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
/* Otherwise, use 2 * root count as initial estimate of result size */
- entries = (Datum *) palloc(sizeof(Datum) * total);
+ init_entries(&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);
+ add_entry(&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));
+ add_entry(&entries, make_scalar_key(&v, v.type == jbvString));
break;
case WJB_VALUE:
- entries[i++] = make_scalar_key(&v, false);
+ add_entry(&entries, make_scalar_key(&v, false));
break;
default:
/* we can ignore structural items */
@@ -114,9 +219,575 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
}
- *nentries = i;
+ *nentries = entries.count;
- PG_RETURN_POINTER(entries);
+ PG_RETURN_POINTER(entries.buf);
+}
+
+/* Append key name to the path (jsonb_ops). */
+static bool
+jsonb_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+ GinJsonPathEntry *pentry;
+ Datum entry;
+
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ path->entries = NULL; /* reset path */
+ return true;
+
+ case jpiKey:
+ {
+ int len;
+ char *key = jspGetString(jsp, &len);
+
+ entry = make_text_key(JGINFLAG_KEY, key, len);
+ break;
+ }
+
+ case jpiAny:
+ case jpiAnyKey:
+ case jpiAnyArray:
+ case jpiIndexArray:
+ entry = PointerGetDatum(NULL);
+ break;
+
+ default:
+ /* other path items like item methods are not supported */
+ return false;
+ }
+
+ pentry = palloc(sizeof(*pentry));
+
+ pentry->type = jsp->type;
+ pentry->entry = entry;
+ pentry->parent = path->entries;
+
+ path->entries = pentry;
+
+ return true;
+}
+
+/* Combine existing path hash with next key hash (jsonb_path_ops). */
+static bool
+jsonb_path_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ path->hash = 0; /* reset path hash */
+ return true;
+
+ case jpiKey:
+ {
+ JsonbValue jbv;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len);
+
+ JsonbHashScalarValue(&jbv, &path->hash);
+ return true;
+ }
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ return true; /* path hash is unchanged */
+
+ default:
+ /* other items (wildcard paths, item methods) are not supported */
+ return false;
+ }
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node(Datum entry)
+{
+ GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args));
+
+ node->type = GIN_JSP_ENTRY;
+ node->val.entryDatum = entry;
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
+{
+ return make_jsp_entry_node(make_scalar_key(scalar, iskey));
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node(GinJsonPathNodeType type, int nargs)
+{
+ GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args) +
+ sizeof(node->args[0]) * nargs);
+
+ node->type = type;
+ node->val.nargs = nargs;
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_args(GinJsonPathNodeType type, List *args)
+{
+ GinJsonPathNode *node = make_jsp_expr_node(type, list_length(args));
+ ListCell *lc;
+ int i = 0;
+
+ foreach(lc, args)
+ node->args[i++] = lfirst(lc);
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_binary(GinJsonPathNodeType type,
+ GinJsonPathNode *arg1, GinJsonPathNode *arg2)
+{
+ GinJsonPathNode *node = make_jsp_expr_node(type, 2);
+
+ node->args[0] = arg1;
+ node->args[1] = arg2;
+
+ return node;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_ops). */
+static List *
+jsonb_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonbValue *scalar, List *nodes)
+{
+ GinJsonPathEntry *pentry;
+
+ /* append path entry nodes */
+ for (pentry = path.entries; pentry; pentry = pentry->parent)
+ {
+ if (pentry->type == jpiKey) /* only keys are indexed */
+ nodes = lappend(nodes, make_jsp_entry_node(pentry->entry));
+ }
+
+ if (scalar)
+ {
+ /* Append scalar node for equality queries. */
+ GinJsonPathNode *node;
+
+ if (scalar->type == jbvString)
+ {
+ GinJsonPathEntry *last = path.entries;
+ GinTernaryValue array_access;
+
+ /*
+ * 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 (cxt->lax)
+ array_access = GIN_MAYBE;
+ else if (!last) /* root ($) */
+ array_access = GIN_FALSE;
+ else if (last->type == jpiAnyArray || last->type == jpiIndexArray)
+ array_access = GIN_TRUE;
+ else if (last->type == jpiAny)
+ array_access = GIN_MAYBE;
+ else
+ array_access = GIN_FALSE;
+
+ if (array_access == GIN_MAYBE)
+ {
+ GinJsonPathNode *n1 = make_jsp_entry_node_scalar(scalar, true);
+ GinJsonPathNode *n2 = make_jsp_entry_node_scalar(scalar, false);
+
+ node = make_jsp_expr_node_binary(GIN_JSP_OR, n1, n2);
+ }
+ else
+ {
+ node = make_jsp_entry_node_scalar(scalar,
+ array_access == GIN_TRUE);
+ }
+ }
+ else
+ {
+ node = make_jsp_entry_node_scalar(scalar, false);
+ }
+
+ nodes = lappend(nodes, node);
+ }
+
+ return nodes;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_path_ops). */
+static List *
+jsonb_path_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonbValue *scalar, List *nodes)
+{
+ if (scalar)
+ {
+ /* append path hash node for equality queries */
+ uint32 hash = path.hash;
+
+ JsonbHashScalarValue(scalar, &hash);
+
+ return lappend(nodes,
+ make_jsp_entry_node(UInt32GetDatum(hash)));
+ }
+ else
+ {
+ /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */
+ return nodes;
+ }
+}
+
+/*
+ * Extract a list of expression nodes that need to be AND-ed by the caller.
+ * Extracted expression is 'path == scalar' if 'scalar' is non-NULL, and
+ * 'EXISTS(path)' otherwise.
+ */
+static List *
+extract_jsp_path_expr_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, JsonbValue *scalar)
+{
+ JsonPathItem next;
+ List *nodes = NIL;
+
+ for (;;)
+ {
+ switch (jsp->type)
+ {
+ case jpiCurrent:
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ GinJsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = extract_jsp_bool_expr(cxt, path, &arg, false);
+
+ if (filter)
+ nodes = lappend(nodes, filter);
+
+ break;
+ }
+
+ default:
+ if (!cxt->add_path_entry(&path, jsp))
+ /*
+ * Path is not supported by the index opclass, return only
+ * the extracted filter nodes.
+ */
+ return nodes;
+ break;
+ }
+
+ if (!jspGetNext(jsp, &next))
+ break;
+
+ jsp = &next;
+ }
+
+ /*
+ * Append nodes from the path expression itself to the already extracted
+ * list of filter nodes.
+ */
+ return cxt->extract_path_nodes(cxt, path, scalar, nodes);
+}
+
+/*
+ * Extract an expression node from one of following jsonpath path expressions:
+ * EXISTS(jsp) (when 'scalar' is NULL)
+ * jsp == scalar (when 'scalar' is not NULL).
+ *
+ * The current path (@) is passed in 'path'.
+ */
+static GinJsonPathNode *
+extract_jsp_path_expr(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, JsonbValue *scalar)
+{
+ /* extract a list of nodes to be AND-ed */
+ List *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
+
+ if (list_length(nodes) <= 0)
+ /* no nodes were extracted => full scan is needed for this path */
+ return NULL;
+
+ if (list_length(nodes) == 1)
+ return linitial(nodes); /* avoid extra AND-node */
+
+ /* construct AND-node for path with filters */
+ return make_jsp_expr_node_args(GIN_JSP_AND, nodes);
+}
+
+/* Recursively extract nodes from the boolean jsonpath expression. */
+static GinJsonPathNode *
+extract_jsp_bool_expr(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, bool not)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd: /* expr && expr */
+ case jpiOr: /* expr || expr */
+ {
+ JsonPathItem arg;
+ GinJsonPathNode *larg;
+ GinJsonPathNode *rarg;
+ GinJsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? GIN_JSP_AND : GIN_JSP_OR;
+
+ return make_jsp_expr_node_binary(type, larg, rarg);
+ }
+
+ case jpiNot: /* !expr */
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ /* extract child expression inverting 'not' flag */
+ return extract_jsp_bool_expr(cxt, path, &arg, !not);
+ }
+
+ case jpiExists: /* EXISTS(path) */
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return NULL; /* NOT EXISTS is not supported */
+
+ jspGetArg(jsp, &arg);
+
+ return extract_jsp_path_expr(cxt, path, &arg, NULL);
+ }
+
+ case jpiNotEqual:
+ /*
+ * 'not' == true case is not supported here because
+ * '!(path != scalar)' is not equivalent to 'path == scalar' in the
+ * general case because of sequence comparison semantics:
+ * 'path == scalar' === 'EXISTS (path, @ == scalar)',
+ * '!(path != scalar)' === 'FOR_ALL(path, @ == scalar)'.
+ * So, we should translate '!(path != scalar)' into GIN query
+ * 'path == scalar || EMPTY(path)', but 'EMPTY(path)' queries
+ * are not supported by the both jsonb opclasses. However in strict
+ * mode we could omit 'EMPTY(path)' part if the path can return
+ * exactly one item (it does not contain wildcard accessors or
+ * item methods like .keyvalue() etc.).
+ */
+ return NULL;
+
+ case jpiEqual: /* path == scalar */
+ {
+ JsonPathItem left_item;
+ JsonPathItem right_item;
+ JsonPathItem *path_item;
+ JsonPathItem *scalar_item;
+ JsonbValue scalar;
+
+
+
+ if (not)
+ return NULL;
+
+ jspGetLeftArg(jsp, &left_item);
+ jspGetRightArg(jsp, &right_item);
+
+ if (jspIsScalar(left_item.type))
+ {
+ scalar_item = &left_item;
+ path_item = &right_item;
+ }
+ else if (jspIsScalar(right_item.type))
+ {
+ scalar_item = &right_item;
+ path_item = &left_item;
+ }
+ else
+ return NULL; /* at least one operand should be a scalar */
+
+ switch (scalar_item->type)
+ {
+ case jpiNull:
+ scalar.type = jbvNull;
+ break;
+ case jpiBool:
+ scalar.type = jbvBool;
+ scalar.val.boolean = !!*scalar_item->content.value.data;
+ break;
+ case jpiNumeric:
+ scalar.type = jbvNumeric;
+ scalar.val.numeric =
+ (Numeric) scalar_item->content.value.data;
+ break;
+ case jpiString:
+ scalar.type = jbvString;
+ scalar.val.string.val = scalar_item->content.value.data;
+ scalar.val.string.len =
+ scalar_item->content.value.datalen;
+ break;
+ default:
+ elog(ERROR, "invalid scalar jsonpath item type: %d",
+ scalar_item->type);
+ return NULL;
+ }
+
+ return extract_jsp_path_expr(cxt, path, path_item, &scalar);
+ }
+
+ default:
+ return NULL; /* not a boolean expression */
+ }
+}
+
+/* Recursively emit all GIN entries found in the node tree */
+static void
+emit_jsp_entries(GinJsonPathNode *node, GinEntries *entries)
+{
+ check_stack_depth();
+
+ switch (node->type)
+ {
+ case GIN_JSP_ENTRY:
+ /* replace datum with its index in the array */
+ node->val.entryIndex = add_entry(entries, node->val.entryDatum);
+ break;
+
+ case GIN_JSP_OR:
+ case GIN_JSP_AND:
+ {
+ int i;
+
+ for (i = 0; i < node->val.nargs; i++)
+ emit_jsp_entries(node->args[i], entries);
+
+ break;
+ }
+ }
+}
+
+/*
+ * Recursively extract GIN entries from jsonpath query.
+ * Root expression node is put into (*extra_data)[0].
+ */
+static Datum *
+extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ GinJsonPathContext cxt;
+ JsonPathItem root;
+ GinJsonPathNode *node;
+ GinJsonPath path = { 0 };
+ GinEntries entries = { 0 };
+
+ cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+ if (pathOps)
+ {
+ cxt.add_path_entry = jsonb_path_ops__add_path_entry;
+ cxt.extract_path_nodes = jsonb_path_ops__extract_path_nodes;
+ }
+ else
+ {
+ cxt.add_path_entry = jsonb_ops__add_path_entry;
+ cxt.extract_path_nodes = jsonb_ops__extract_path_nodes;
+ }
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? extract_jsp_path_expr(&cxt, path, &root, NULL)
+ : extract_jsp_bool_expr(&cxt, path, &root, false);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ emit_jsp_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;
+}
+
+/*
+ * Recursively execute jsonpath expression.
+ * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag.
+ */
+static GinTernaryValue
+execute_jsp_expr(GinJsonPathNode *node, void *check, bool ternary)
+{
+ GinTernaryValue res;
+ GinTernaryValue v;
+ int i;
+
+ switch (node->type)
+ {
+ case GIN_JSP_AND:
+ res = GIN_TRUE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = execute_jsp_expr(node->args[i], check, ternary);
+ if (v == GIN_FALSE)
+ return GIN_FALSE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case GIN_JSP_OR:
+ res = GIN_FALSE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = execute_jsp_expr(node->args[i], check, ternary);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case GIN_JSP_ENTRY:
+ {
+ 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 +852,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 = extract_jsp_query(jp, strategy, false, nentries,
+ extra_data);
+
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
else
{
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -199,7 +882,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 +939,22 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+
+ if (nkeys <= 0)
+ {
+ res = true;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ false) != GIN_FALSE;
+ }
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +969,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 +1006,20 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ if (nkeys <= 0)
+ {
+ res = GIN_MAYBE;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ true);
+ }
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -331,14 +1043,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 +1059,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);
+ init_entries(&entries, 2 * total);
/* We keep a stack of partial hashes corresponding to parent key levels */
tail.parent = NULL;
@@ -361,13 +1072,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 +1102,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);
+ add_entry(&entries, UInt32GetDatum(stack->hash));
/* reset hash for next key, value, or sub-object */
stack->hash = stack->parent->hash;
break;
@@ -419,9 +1123,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 +1136,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)));
+
+ /* ... 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);
- /* 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)));
+ entries = extract_jsp_query(jp, strategy, true, nentries,
+ extra_data);
- /* ... although "contains {}" requires a full index scan */
- if (*nentries == 0)
- *searchMode = GIN_SEARCH_MODE_ALL;
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+ entries = NULL;
+ }
PG_RETURN_POINTER(entries);
}
@@ -456,32 +1177,49 @@ 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)
+ {
+ *recheck = true;
+
+ if (nkeys <= 0)
+ {
+ res = true;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ false) != GIN_FALSE;
}
}
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1232,42 @@ 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)
+ {
+ if (nkeys <= 0)
+ {
+ res = GIN_MAYBE;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) 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 075a54c..b2d226f 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 76e98eb..a0f972f 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 5d16131..b3cf4c2 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 4fddd2d..c714b5e 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2731,6 +2731,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}';
@@ -2806,6 +2914,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';
@@ -2956,6 +3254,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 6072f6b..719549b 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 6cbdfe4..d6dcd73 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -740,6 +740,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;
@@ -758,6 +776,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';
@@ -807,6 +858,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;
--
2.7.4
0006-Jsonpath-syntax-extensions-v21.patch (text/x-patch, 47.3K) download
From 6662a633c31aec2b76af09b9e49992183b9fdce9 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Tue, 4 Dec 2018 02:05:11 +0300
Subject: [PATCH 06/13] Jsonpath syntax extensions
---
src/backend/utils/adt/jsonpath.c | 153 +++++++++-
src/backend/utils/adt/jsonpath_exec.c | 422 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 ++++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 ++++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 +++++
src/test/regress/sql/json_jsonpath.sql | 47 +++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1242 insertions(+), 91 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11d457d..456db2e 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -136,12 +136,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
nestingLevel + argNestingLevel,
insideArraySubscript);
@@ -218,6 +221,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -305,6 +363,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -494,12 +554,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -563,6 +623,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -585,7 +693,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -688,6 +796,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -699,6 +808,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -713,7 +832,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -765,7 +885,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -869,3 +992,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index b79e01a..cded305 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -84,6 +84,8 @@ static inline JsonPathExecResult recursiveExecuteNested(JsonPathExecContext *cxt
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1686,7 +1688,116 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{
int innermostArraySize = cxt->innermostArraySize;
int i;
@@ -1787,9 +1898,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -2067,7 +2182,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2078,99 +2192,130 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- text *template;
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- template = cstring_to_text_with_len(template_str,
- template_len);
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tryToParseDatetime(template, datetime, tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ template = cstring_to_text_with_len(template_str,
+ template_len);
- if (tzname)
- pfree(tzname);
- }
- else
- {
- /* Try to recognize one of ISO formats. */
- static const char *fmt_str[] =
- {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- /* cache for format texts */
- static text *fmt_txt[lengthof(fmt_str)] = { 0 };
- int i;
-
- for (i = 0; i < lengthof(fmt_str); i++)
+ if (tryToParseDatetime(template, datetime, tzname,
+ false, &value, &typid, &typmod,
+ &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (!fmt_txt[i])
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
{
- MemoryContext oldcxt =
- MemoryContextSwitchTo(TopMemoryContext);
-
- fmt_txt[i] = cstring_to_text(fmt_str[i]);
- MemoryContextSwitchTo(oldcxt);
- }
-
- if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
- &value, &typid, &typmod, &tz))
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
{
- res = jperOk;
- break;
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL,
+ true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2300,6 +2445,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 3856a06..be1d488 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -255,6 +255,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -288,9 +308,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -314,7 +334,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -327,6 +347,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -381,6 +415,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index b3cf4c2..3747985 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -84,6 +84,10 @@ typedef enum JsonPathItemType {
jpiLast, /* LAST array subscript */
jpiStartsWith, /* STARTS WITH predicate */
jpiLikeRegex, /* LIKE_REGEX predicate */
+ jpiSequence, /* sequence constructor: 'expr, ...' */
+ jpiArray, /* array constructor: '[expr, ...]' */
+ jpiObject, /* object constructor: '{ key : value, ... }' */
+ jpiObjectField, /* element of object constructor: 'key : value' */
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -138,6 +142,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -164,6 +181,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -209,6 +229,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 942bf6b..87da5ed 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index f93c930..feb094c 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 193fc68..ea29105 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 824f510..0901876 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 43f34ef..ad7a320 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 8a3ea42..653f928 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-05 00:11 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-05 00:11 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Dec 4, 2018 at 2:23 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> 2) We define both DCH_FF# and DCH_ff#, but we never ever use the
> lower-case version. Heck, it's not mentioned even in DCH_keywords, which
> does this:
>
> ...
> {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> ...
> {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> ...
>
> Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
> "day".
>
> Yes, "ff#" are mapped to DCH_FF# like "mi" is mapped DCH_MI.
>
> "Day", "day" are not mapped to DCH_DAY because they determine letter case in the
> output, but "ff1" and "FF#" output contains only digits.
Right, DCH_poz is also offset in DCH_keywords array. So, if array has
an entry for "ff1" then enum should have a DCH_ff1 member in the same
position.
I got some other questions regarding this patchset.
1) Why do we parse FF7-FF9 if we're not supporting them anyway?
Without defining FF7-FF9 we can also don't throw errors for them
everywhere. That would save us some code lines.
2) + DCH_to_char_fsec("%01d", in->fsec / INT64CONST(100000));
Why do we use INT64CONST() here and in the similar places assuming
that fsec is only uint32?
3) wrapItem() is unused in
0002-Jsonpath-engine-and-operators-v21.patch, but used in
0006-Jsonpath-syntax-extensions-v21.patch. Please, move it to
0006-Jsonpath-syntax-extensions-v21.patch?
4) I also got these couple of warning during compilation.
jsonpath_exec.c:1485:1: warning: unused function
'recursiveExecuteNested' [-Wunused-function]
recursiveExecuteNested(JsonPathExecContext *cxt, JsonPathItem *jsp,
^
1 warning generated.
jsonpath_scan.l:444:6: warning: implicit declaration of function
'jsonpath_yyparse' is invalid in C99 [-Wimplicit-function-declaration]
if (jsonpath_yyparse((void*)&parseresult) != 0)
^
1 warning generated.
Perhaps recursiveExecuteNested() is unsed in this patchset. It's
probably used by some subsequent SQL/JSON-related patchset. So,
please, move it there.
5) I think each usage of PG_TRY()/PG_CATCH() deserves comment
describing why it's safe to use without subtransaction. In fact we
should just state that no function called inside performs data
modification.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-05 14:21 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2019-01-05 14:21 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 1/5/19 1:11 AM, Alexander Korotkov wrote:
> On Tue, Dec 4, 2018 at 2:23 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
>> 2) We define both DCH_FF# and DCH_ff#, but we never ever use the
>> lower-case version. Heck, it's not mentioned even in DCH_keywords, which
>> does this:
>>
>> ...
>> {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
>> ...
>> {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
>> ...
>>
>> Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
>> "day".
>>
>> Yes, "ff#" are mapped to DCH_FF# like "mi" is mapped DCH_MI.
>>
>> "Day", "day" are not mapped to DCH_DAY because they determine letter case in the
>> output, but "ff1" and "FF#" output contains only digits.
>
> Right, DCH_poz is also offset in DCH_keywords array. So, if array has
> an entry for "ff1" then enum should have a DCH_ff1 member in the same
> position.
>
I guess my question is why we define DCH_ff# at all, when it's not
mapped in DCH_keywords? ISTM we could simply leave them out of all the
arrays, no? Of course, this is not specific to this patch, as it applies
to pre-existing items (like DCH_mi).
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-05 20:52 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-05 20:52 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sat, Jan 5, 2019 at 5:21 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> On 1/5/19 1:11 AM, Alexander Korotkov wrote:
> > On Tue, Dec 4, 2018 at 2:23 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> >> 2) We define both DCH_FF# and DCH_ff#, but we never ever use the
> >> lower-case version. Heck, it's not mentioned even in DCH_keywords, which
> >> does this:
> >>
> >> ...
> >> {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> >> ...
> >> {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
> >> ...
> >>
> >> Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
> >> "day".
> >>
> >> Yes, "ff#" are mapped to DCH_FF# like "mi" is mapped DCH_MI.
> >>
> >> "Day", "day" are not mapped to DCH_DAY because they determine letter case in the
> >> output, but "ff1" and "FF#" output contains only digits.
> >
> > Right, DCH_poz is also offset in DCH_keywords array. So, if array has
> > an entry for "ff1" then enum should have a DCH_ff1 member in the same
> > position.
> >
>
> I guess my question is why we define DCH_ff# at all, when it's not
> mapped in DCH_keywords? ISTM we could simply leave them out of all the
> arrays, no?
I think we still need separate array entries for "FF1" and "ff1". At
least, as long as we don't allow "fF1" and "Ff1". In order to get rid
of DCH_ff#, I think we should decouple DCH_keywords from array
indexes.
> Of course, this is not specific to this patch, as it applies
> to pre-existing items (like DCH_mi).
Right, that should be a subject of separate patch.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-11 22:21 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2019-01-11 22:21 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 23rd version of the patches.
On 05.01.2019 3:11, Alexander Korotkov wrote:
> On Tue, Dec 4, 2018 at 2:23 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
>> 2) We define both DCH_FF# and DCH_ff#, but we never ever use the
>> lower-case version. Heck, it's not mentioned even in DCH_keywords, which
>> does this:
>>
>> ...
>> {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
>> ...
>> {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
>> ...
>>
>> Compare that to DCH_DAY, DCH_Day and DCH_day, mapped to "DAY", "Day" and
>> "day".
>>
>> Yes, "ff#" are mapped to DCH_FF# like "mi" is mapped DCH_MI.
>>
>> "Day", "day" are not mapped to DCH_DAY because they determine letter case in the
>> output, but "ff1" and "FF#" output contains only digits.
> Right, DCH_poz is also offset in DCH_keywords array. So, if array has
> an entry for "ff1" then enum should have a DCH_ff1 member in the same
> position.
>
> I got some other questions regarding this patchset.
>
> 1) Why do we parse FF7-FF9 if we're not supporting them anyway?
> Without defining FF7-FF9 we can also don't throw errors for them
> everywhere. That would save us some code lines.
FF7-FF9 were removed.
> 2) + DCH_to_char_fsec("%01d", in->fsec / INT64CONST(100000));
> Why do we use INT64CONST() here and in the similar places assuming
> that fsec is only uint32?
Fixed.
> 3) wrapItem() is unused in
> 0002-Jsonpath-engine-and-operators-v21.patch, but used in
> 0006-Jsonpath-syntax-extensions-v21.patch. Please, move it to
> 0006-Jsonpath-syntax-extensions-v21.patch?
wraptItem() was moved into patch #6.
> 4) I also got these couple of warning during compilation.
>
> jsonpath_exec.c:1485:1: warning: unused function
> 'recursiveExecuteNested' [-Wunused-function]
> recursiveExecuteNested(JsonPathExecContext *cxt, JsonPathItem *jsp,
> ^
> 1 warning generated.
> jsonpath_scan.l:444:6: warning: implicit declaration of function
> 'jsonpath_yyparse' is invalid in C99 [-Wimplicit-function-declaration]
> if (jsonpath_yyparse((void*)&parseresult) != 0)
> ^
> 1 warning generated.
>
> Perhaps recursiveExecuteNested() is unsed in this patchset. It's
> probably used by some subsequent SQL/JSON-related patchset. So,
> please, move it there.
recursiveExecuteNested() was removed from this patch set.
Prototype for jsonpath_yyparse() should be in the Bison-generated file
src/include/adt/jsonpath_gram.h.
> 5) I think each usage of PG_TRY()/PG_CATCH() deserves comment
> describing why it's safe to use without subtransaction. In fact we
> should just state that no function called inside performs data
> modification.
Comments were added.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (6 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v23.patch (text/x-patch, 31.5K) download
From e32803e767dac17539c4800c190cb8da28696313 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:21 +0300
Subject: [PATCH 01/13] Preliminary datetime infrastructure
---
doc/src/sgml/func.sgml | 24 ++
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 407 ++++++++++++++++++++++++++++--
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 79 ++++++
src/test/regress/expected/timestamp.out | 16 ++
src/test/regress/expected/timestamptz.out | 15 ++
src/test/regress/sql/horology.sql | 9 +
src/test/regress/sql/timestamp.sql | 8 +
src/test/regress/sql/timestamptz.sql | 8 +
13 files changed, 554 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 90d67f1..d1febaf 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5994,6 +5994,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry>microsecond (000000-999999)</entry>
</row>
<row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
+ <row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
</row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a..dfe4453 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 096d862..7f540b7 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,13 +3052,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;
@@ -3149,8 +3196,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3374,6 +3431,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3468,109 @@ 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_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3859,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3879,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);
}
@@ -3718,7 +3900,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3922,175 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
+/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4098,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' 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, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4160,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4348,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a..5103cd4 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 bec129a..bd15bfa 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 f5ec9bb..8a6f2cd 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 5b275dc..227779c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..74ecb7c 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..44f7cc9 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,22 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..cdd3c14 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..3c85803 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..6a09dc2 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..588c3e0 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
--
2.7.4
0002-Jsonpath-engine-and-operators-v23.patch (text/x-patch, 318.9K) download
From 45b17b4cb9cde177e22ec172dbf4b3dcdb53faea Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:21 +0300
Subject: [PATCH 02/13] Jsonpath engine and operators
---
src/backend/Makefile | 11 +-
src/backend/lib/stringinfo.c | 21 +
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 20 +-
src/backend/utils/adt/json.c | 890 +++++++-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 37 +-
src/backend/utils/adt/jsonpath.c | 871 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2816 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 495 +++++
src/backend/utils/adt/jsonpath_json.c | 22 +
src/backend/utils/adt/jsonpath_scan.l | 629 ++++++
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 28 +
src/include/catalog/pg_proc.dat | 65 +
src/include/catalog/pg_type.dat | 5 +
src/include/lib/stringinfo.h | 6 +
src/include/regex/regex.h | 5 +
src/include/utils/.gitignore | 1 +
src/include/utils/jsonapi.h | 64 +-
src/include/utils/jsonb.h | 39 +-
src/include/utils/jsonpath.h | 290 +++
src/include/utils/jsonpath_json.h | 106 +
src/include/utils/jsonpath_scanner.h | 30 +
src/test/regress/expected/json_jsonpath.out | 1732 ++++++++++++++++
src/test/regress/expected/jsonb_jsonpath.out | 1711 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 800 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 3 +
src/test/regress/sql/json_jsonpath.sql | 379 ++++
src/test/regress/sql/jsonb_jsonpath.sql | 385 ++++
src/test/regress/sql/jsonpath.sql | 146 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
35 files changed, 11621 insertions(+), 48 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_json.c
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_json.h
create mode 100644 src/include/utils/jsonpath_scanner.h
create mode 100644 src/test/regress/expected/json_jsonpath.out
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/json_jsonpath.sql
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 478a96d..31d9d66 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
@@ -308,6 +316,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 99c83c1..d2cdd04 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 0000000..7fab054
--- /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 20eead1..8db7f98 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 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 \
orderedsetaggs.o partitionfuncs.o pg_locale.o pg_lsn.o \
@@ -32,6 +33,23 @@ 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_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index de0d072..f5e1e88 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 =
{
@@ -127,6 +130,27 @@ lex_peek(JsonLexContext *lex)
}
/*
+ * lex_peek_value
+ *
+ * get the current look_ahead de-escaped lexeme.
+*/
+static inline char *
+lex_peek_value(JsonLexContext *lex)
+{
+ if (lex->token_type == JSON_TOKEN_STRING)
+ return lex->strval ? pstrdup(lex->strval->data) : NULL;
+ else
+ {
+ int len = lex->token_terminator - lex->token_start;
+ char *tokstr = palloc(len + 1);
+
+ memcpy(tokstr, lex->token_start, len);
+ tokstr[len] = '\0';
+ return tokstr;
+ }
+}
+
+/*
* lex_accept
*
* accept the look_ahead token and move the lexer to the next token if the
@@ -141,22 +165,8 @@ lex_accept(JsonLexContext *lex, JsonTokenType token, char **lexeme)
if (lex->token_type == token)
{
if (lexeme != NULL)
- {
- if (lex->token_type == JSON_TOKEN_STRING)
- {
- if (lex->strval != NULL)
- *lexeme = pstrdup(lex->strval->data);
- }
- else
- {
- int len = (lex->token_terminator - lex->token_start);
- char *tokstr = palloc(len + 1);
+ *lexeme = lex_peek_value(lex);
- memcpy(tokstr, lex->token_start, len);
- tokstr[len] = '\0';
- *lexeme = tokstr;
- }
- }
json_lex(lex);
return true;
}
@@ -1506,7 +1516,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 +1524,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 +1532,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1560,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1641,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),
@@ -2553,3 +2583,821 @@ json_typeof(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(type));
}
+
+/*
+ * Initialize a JsonContainer from a json text, its type and size.
+ * 'type' can be JB_FOBJECT, JB_FARRAY, (JB_FARRAY | JB_FSCALAR).
+ * 'size' is a number of elements/pairs in array/object, or -1 if unknown.
+ */
+static void
+jsonInitContainer(JsonContainerData *jc, char *json, int len, int type,
+ int size)
+{
+ if (size < 0 || size > JB_CMASK)
+ size = JB_CMASK; /* unknown size */
+
+ jc->data = json;
+ jc->len = len;
+ jc->header = type | size;
+}
+
+/*
+ * Initialize a JsonContainer from a text datum.
+ */
+static void
+jsonInit(JsonContainerData *jc, Datum value)
+{
+ text *json = DatumGetTextP(value);
+ JsonLexContext *lex = makeJsonLexContext(json, false);
+ JsonTokenType tok;
+ int type;
+ int size = -1;
+
+ /* Lex exactly one token from the input and check its type. */
+ json_lex(lex);
+ tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ type = JB_FOBJECT;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_OBJECT_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ type = JB_FARRAY;
+ lex_accept(lex, tok, NULL);
+ if (lex_peek(lex) == JSON_TOKEN_ARRAY_END)
+ size = 0;
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ type = JB_FARRAY | JB_FSCALAR;
+ size = 1;
+ break;
+ default:
+ elog(ERROR, "unexpected json token: %d", tok);
+ type = jbvNull;
+ break;
+ }
+
+ pfree(lex);
+
+ jsonInitContainer(jc, VARDATA(json), VARSIZE(json) - VARHDRSZ, type, size);
+}
+
+/*
+ * Wrap JSON text into a palloc()'d Json structure.
+ */
+Json *
+JsonCreate(text *json)
+{
+ Json *res = palloc0(sizeof(*res));
+
+ jsonInit((JsonContainerData *) &res->root, PointerGetDatum(json));
+
+ return res;
+}
+
+/*
+ * Fill JsonbValue from the current iterator token.
+ * Returns true if recursion into nested object or array is needed (in this case
+ * child iterator is created and put into *pit).
+ */
+static bool
+jsonFillValue(JsonIterator **pit, JsonbValue *res, bool skipNested,
+ JsontIterState nextState)
+{
+ JsonIterator *it = *pit;
+ JsonLexContext *lex = it->lex;
+ JsonTokenType tok = lex_peek(lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_NULL:
+ res->type = jbvNull;
+ break;
+
+ case JSON_TOKEN_TRUE:
+ res->type = jbvBool;
+ res->val.boolean = true;
+ break;
+
+ case JSON_TOKEN_FALSE:
+ res->type = jbvBool;
+ res->val.boolean = false;
+ break;
+
+ case JSON_TOKEN_STRING:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvString;
+ res->val.string.val = token;
+ res->val.string.len = strlen(token);
+ break;
+ }
+
+ case JSON_TOKEN_NUMBER:
+ {
+ char *token = lex_peek_value(lex);
+ res->type = jbvNumeric;
+ res->val.numeric = DatumGetNumeric(DirectFunctionCall3(
+ numeric_in, CStringGetDatum(token), 0, -1));
+ break;
+ }
+
+ case JSON_TOKEN_OBJECT_START:
+ case JSON_TOKEN_ARRAY_START:
+ {
+ JsonContainerData *cont = palloc(sizeof(*cont));
+ char *token_start = lex->token_start;
+ int len;
+
+ if (skipNested)
+ {
+ /* find the end of a container for its length calculation */
+ if (tok == JSON_TOKEN_OBJECT_START)
+ parse_object(lex, &nullSemAction);
+ else
+ parse_array(lex, &nullSemAction);
+
+ len = lex->token_start - token_start;
+ }
+ else
+ len = lex->input_length - (lex->token_start - lex->input);
+
+ jsonInitContainer(cont,
+ token_start, len,
+ tok == JSON_TOKEN_OBJECT_START ?
+ JB_FOBJECT : JB_FARRAY,
+ -1);
+
+ res->type = jbvBinary;
+ res->val.binary.data = (JsonbContainer *) cont;
+ res->val.binary.len = len;
+
+ if (skipNested)
+ return false;
+
+ /* recurse into container */
+ it->state = nextState;
+ *pit = JsonIteratorInitFromLex(cont, lex, *pit);
+ return true;
+ }
+
+ default:
+ report_parse_error(JSON_PARSE_VALUE, lex);
+ }
+
+ lex_accept(lex, tok, NULL);
+
+ return false;
+}
+
+/*
+ * Free the topmost entry in the stack of JsonIterators.
+ */
+static inline JsonIterator *
+JsonIteratorFreeAndGetParent(JsonIterator *it)
+{
+ JsonIterator *parent = it->parent;
+
+ pfree(it);
+
+ return parent;
+}
+
+/*
+ * Free the entire stack of JsonIterators.
+ */
+void
+JsonIteratorFree(JsonIterator *it)
+{
+ while (it)
+ it = JsonIteratorFreeAndGetParent(it);
+}
+
+/*
+ * Get next JsonbValue while iterating through JsonContainer.
+ *
+ * For more details, see JsonbIteratorNext().
+ */
+JsonbIteratorToken
+JsonIteratorNext(JsonIterator **pit, JsonbValue *val, bool skipNested)
+{
+ JsonIterator *it;
+
+ if (*pit == NULL)
+ return WJB_DONE;
+
+recurse:
+ it = *pit;
+
+ /* parse by recursive descent */
+ switch (it->state)
+ {
+ case JTI_ARRAY_START:
+ val->type = jbvArray;
+ val->val.array.nElems = it->isScalar ? 1 : -1;
+ val->val.array.rawScalar = it->isScalar;
+ val->val.array.elems = NULL;
+ it->state = it->isScalar ? JTI_ARRAY_ELEM_SCALAR : JTI_ARRAY_ELEM;
+ return WJB_BEGIN_ARRAY;
+
+ case JTI_ARRAY_ELEM_SCALAR:
+ {
+ (void) jsonFillValue(pit, val, skipNested, JTI_ARRAY_END);
+ it->state = JTI_ARRAY_END;
+ return WJB_ELEM;
+ }
+
+ case JTI_ARRAY_END:
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_ARRAY;
+
+ case JTI_ARRAY_ELEM:
+ if (lex_accept(it->lex, JSON_TOKEN_ARRAY_END, NULL))
+ {
+ it->state = JTI_ARRAY_END;
+ goto recurse;
+ }
+
+ if (jsonFillValue(pit, val, skipNested, JTI_ARRAY_ELEM_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_ARRAY_ELEM_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_ARRAY_END)
+ report_parse_error(JSON_PARSE_ARRAY_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_ARRAY_ELEM_AFTER)
+ {
+ it->state = JTI_ARRAY_ELEM;
+ goto recurse;
+ }
+
+ return WJB_ELEM;
+
+ case JTI_OBJECT_START:
+ val->type = jbvObject;
+ val->val.object.nPairs = -1;
+ val->val.object.pairs = NULL;
+ val->val.object.uniquify = false;
+ it->state = JTI_OBJECT_KEY;
+ return WJB_BEGIN_OBJECT;
+
+ case JTI_OBJECT_KEY:
+ if (lex_accept(it->lex, JSON_TOKEN_OBJECT_END, NULL))
+ {
+ if (!it->parent && lex_peek(it->lex) != JSON_TOKEN_END)
+ report_parse_error(JSON_PARSE_END, it->lex);
+ *pit = JsonIteratorFreeAndGetParent(*pit);
+ return WJB_END_OBJECT;
+ }
+
+ if (lex_peek(it->lex) != JSON_TOKEN_STRING)
+ report_parse_error(JSON_PARSE_OBJECT_START, it->lex);
+
+ (void) jsonFillValue(pit, val, true, JTI_OBJECT_VALUE);
+
+ if (!lex_accept(it->lex, JSON_TOKEN_COLON, NULL))
+ report_parse_error(JSON_PARSE_OBJECT_LABEL, it->lex);
+
+ it->state = JTI_OBJECT_VALUE;
+ return WJB_KEY;
+
+ case JTI_OBJECT_VALUE:
+ if (jsonFillValue(pit, val, skipNested, JTI_OBJECT_VALUE_AFTER))
+ goto recurse;
+
+ /* fall through */
+
+ case JTI_OBJECT_VALUE_AFTER:
+ if (!lex_accept(it->lex, JSON_TOKEN_COMMA, NULL))
+ {
+ if (lex_peek(it->lex) != JSON_TOKEN_OBJECT_END)
+ report_parse_error(JSON_PARSE_OBJECT_NEXT, it->lex);
+ }
+
+ if (it->state == JTI_OBJECT_VALUE_AFTER)
+ {
+ it->state = JTI_OBJECT_KEY;
+ goto recurse;
+ }
+
+ it->state = JTI_OBJECT_KEY;
+ return WJB_VALUE;
+
+ default:
+ break;
+ }
+
+ return WJB_DONE;
+}
+
+/* Initialize JsonIterator from json lexer which onto the first token. */
+static JsonIterator *
+JsonIteratorInitFromLex(JsonContainer *jc, JsonLexContext *lex,
+ JsonIterator *parent)
+{
+ JsonIterator *it = palloc(sizeof(JsonIterator));
+ JsonTokenType tok;
+
+ it->container = jc;
+ it->parent = parent;
+ it->lex = lex;
+
+ tok = lex_peek(it->lex);
+
+ switch (tok)
+ {
+ case JSON_TOKEN_OBJECT_START:
+ it->isScalar = false;
+ it->state = JTI_OBJECT_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_ARRAY_START:
+ it->isScalar = false;
+ it->state = JTI_ARRAY_START;
+ lex_accept(it->lex, tok, NULL);
+ break;
+ case JSON_TOKEN_STRING:
+ case JSON_TOKEN_NUMBER:
+ case JSON_TOKEN_TRUE:
+ case JSON_TOKEN_FALSE:
+ case JSON_TOKEN_NULL:
+ it->isScalar = true;
+ it->state = JTI_ARRAY_START;
+ break;
+ default:
+ report_parse_error(JSON_PARSE_VALUE, it->lex);
+ }
+
+ return it;
+}
+
+/*
+ * Given a JsonContainer, expand to JsonIterator to iterate over items
+ * fully expanded to in-memory representation for manipulation.
+ *
+ * See JsonbIteratorNext() for notes on memory management.
+ */
+JsonIterator *
+JsonIteratorInit(JsonContainer *jc)
+{
+ JsonLexContext *lex = makeJsonLexContextCstringLen(jc->data, jc->len, true);
+ json_lex(lex);
+ return JsonIteratorInitFromLex(jc, lex, NULL);
+}
+
+/*
+ * Serialize a single JsonbValue into text buffer.
+ */
+static void
+JsonEncodeJsonbValue(StringInfo buf, JsonbValue *jbv)
+{
+ check_stack_depth();
+
+ switch (jbv->type)
+ {
+ case jbvNull:
+ appendBinaryStringInfo(buf, "null", 4);
+ break;
+
+ case jbvBool:
+ if (jbv->val.boolean)
+ appendBinaryStringInfo(buf, "true", 4);
+ else
+ appendBinaryStringInfo(buf, "false", 5);
+ break;
+
+ case jbvNumeric:
+ 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,
+ &jbv->val.datetime.tz);
+
+ 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 "uniquify" flag in objects.
+ */
+JsonbValue *
+pushJsonValue(JsonbParseState **pstate, JsonbIteratorToken seq,
+ JsonbValue *jbval)
+{
+ JsonIterator *it;
+ JsonbValue *res = NULL;
+ JsonbValue v;
+ JsonbIteratorToken tok;
+
+ if (!jbval || (seq != WJB_ELEM && seq != WJB_VALUE) ||
+ jbval->type != jbvBinary)
+ {
+ /* drop through */
+ res = pushJsonbValueScalar(pstate, seq, jbval);
+
+ /* reset "uniquify" flag of objects */
+ if (seq == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+
+ return res;
+ }
+
+ /* unpack the binary and add each piece to the pstate */
+ it = JsonIteratorInit((JsonContainer *) jbval->val.binary.data);
+ while ((tok = JsonIteratorNext(&it, &v, false)) != WJB_DONE)
+ {
+ res = pushJsonbValueScalar(pstate, tok,
+ tok < WJB_BEGIN_ARRAY ? &v : NULL);
+
+ /* reset "uniquify" flag of objects */
+ if (tok == WJB_BEGIN_OBJECT)
+ (*pstate)->contVal.val.object.uniquify = false;
+ }
+
+ return res;
+}
+
+/*
+ * Extract scalar JsonbValue from a scalar json.
+ */
+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.c b/src/backend/utils/adt/jsonb.c
index c02c856..78f1506 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363..915a8fd 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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.uniquify = true;
break;
case WJB_KEY:
Assert(scalarVal->type == jbvString);
@@ -822,6 +822,7 @@ recurse:
/* Set v to object on first object call */
val->type = jbvObject;
val->val.object.nPairs = (*it)->nElems;
+ val->val.object.uniquify = true;
/*
* v->val.object.pairs is not actually set, because we aren't
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,6 +1840,9 @@ uniqueifyJsonbObject(JsonbValue *object)
Assert(object->type == jbvObject);
+ if (!object->val.object.uniquify)
+ return;
+
if (object->val.object.nPairs > 1)
qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
lengthCompareJsonbPair, &hasNonUniq);
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..11d457d
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..ccfd40e
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2816 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "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/formatting.h"
+#include "utils/json.h"
+#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
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ 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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+#ifndef JSONPATH_JSON_C
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+#endif
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONXOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction
+ * because no function called inside performs data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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 :
+#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);
+#endif
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Public interface to jsonpath executor
+ */
+JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 = JSONXOID;
+ jpv->cb_arg = DatumGetPointer(JsonbPGetDatum(JsonbValueToJsonb(&v)));
+ break;
+ default:
+ elog(ERROR, "invalid jsonb value type");
+ }
+
+ vars = lappend(vars, jpv);
+ }
+ }
+
+ return vars;
+}
+
+static void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+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 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)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query2(PG_FUNCTION_ARGS)
+{
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+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)
+{
+ JsonbParseState *ps = NULL;
+ JsonValueListIterator it = { 0 };
+ JsonbValue *jbv;
+
+ pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
+
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..3856a06
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_json.c b/src/backend/utils/adt/jsonpath_json.c
new file mode 100644
index 0000000..91b3e7b
--- /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/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..012907f
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,629 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a92..da13a87 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6..7287653 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..c4037d4 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,33 @@
{ 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' },
+{ 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 3ecc2e1..159fe4b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,71 @@
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 => 'implementation of @? operator',
+ 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',
+ 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' },
+{ oid => '6057', descr => 'jsonpath query',
+ 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' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ 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/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index 4b7750d..e44c562 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 729b8bc..96b8fe5 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a..e0705e1 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9..5775ecf 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
@@ -161,6 +204,25 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
+
+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 6ccacf5..06ae0b0 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;
@@ -219,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
@@ -236,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,
};
/*
@@ -270,6 +279,8 @@ struct JsonbValue
{
int nPairs; /* 1 pair, 2 elements */
JsonbPair *pairs;
+ bool uniquify; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
} object; /* Associative container type */
struct
@@ -277,11 +288,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,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,
@@ -363,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);
@@ -379,5 +403,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 0000000..5d16131
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,290 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+JsonPathExecResult executeJsonPath(JsonPath *path,
+ List *vars, /* list of JsonPathVariable */
+ Jsonb *json,
+ JsonValueList *foundJson);
+
+#endif
diff --git a/src/include/utils/jsonpath_json.h b/src/include/utils/jsonpath_json.h
new file mode 100644
index 0000000..064d77e
--- /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/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..1c8447f
--- /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(JsonPathParseResult **result, const char *message);
+
+#endif
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
new file mode 100644
index 0000000..942bf6b
--- /dev/null
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -0,0 +1,1732 @@
+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 $.**{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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"b": 1}}' @* 'lax $.**{1 to 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 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 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 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"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")';
+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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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")';
+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"
+(1 row)
+
+select json '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select json '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-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"]'
+ @* '$[*].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-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"]'
+ @* '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))';
+ ?column?
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04: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-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"]'
+ @* '$[*].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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select json '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT json '[{"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/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
new file mode 100644
index 0000000..f93c930
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1]' @* 'strict $[1]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.a';
+ ?column?
+----------
+ 12
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.b';
+ ?column?
+-----------
+ {"a": 13}
+(1 row)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* '$.*';
+ ?column?
+-----------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb '{"a": 12, "b": {"a": 13}}' @* 'lax $.*.a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[*].*';
+ ?column?
+----------
+ 13
+ 14
+(2 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0].a';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[1].a';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[2].a';
+ ?column?
+----------
+(0 rows)
+
+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';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
+ ?column?
+-----------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb '1' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '1' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[0]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* 'lax $[*]';
+ ?column?
+----------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb '[]' @* '$[last]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $[last]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @* '$[last]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last - 1]';
+ ?column?
+----------
+ 2
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
+ ?column?
+----------
+ 3
+(1 row)
+
+select jsonb '[1,2,3]' @* '$[last ? (@.type() == "string")]';
+ERROR: Invalid SQL/JSON subscript
+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)
+
+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 jsonb '{"a": {"b": 1}}' @* 'lax $.**';
+ ?column?
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 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 to last}';
+ ?column?
+----------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{2 to last}';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{3 to last}';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1}.b ? (@ > 0)';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{0 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to last}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{1 to 2}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @* 'lax $.**{2 to 3}.b ? (@ > 0)';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x))';
+ ?column?
+----------
+ {"x": 2}
+(1 row)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.y))';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"g": {"x": 2}}' @* '$.g ? (exists (@.x ? (@ >= 2) ))';
+ ?column?
+----------
+ {"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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == 1 + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (1 + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == .b + 1)';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @* '$.** ? (.a == (.b + 1))';
+ ?column?
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ 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: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb '{"a": [2]}' @* 'lax $.a * 3';
+ ?column?
+----------
+ 6
+(1 row)
+
+select jsonb '{"a": [2]}' @* 'lax $.a + 3';
+ ?column?
+----------
+ 5
+(1 row)
+
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+ ?column?
+----------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb '2' @* '$ > 1';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '2' @* '$ <= 1';
+ ?column?
+----------
+ false
+(1 row)
+
+select jsonb '2' @* '$ == "2"';
+ ?column?
+----------
+ null
+(1 row)
+
+select jsonb '2' @~ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @~ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @~ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @~ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @~ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ f
+(1 row)
+
+select jsonpath_predicate(jsonb '[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonpath_predicate
+--------------------
+ t
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* 'lax $.type()';
+ ?column?
+----------
+ "array"
+(1 row)
+
+select jsonb '[null,1,true,"a",[],{}]' @* '$[*].type()';
+ ?column?
+-----------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb 'null' @* 'null.type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb 'null' @* 'true.type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb 'null' @* '123.type()';
+ ?column?
+----------
+ "number"
+(1 row)
+
+select jsonb 'null' @* '"123".type()';
+ ?column?
+----------
+ "string"
+(1 row)
+
+select jsonb '{"a": 2}' @* '($.a - 5).abs() + 10';
+ ?column?
+----------
+ 13
+(1 row)
+
+select jsonb '{"a": 2.5}' @* '-($.a * $.a).floor() + 10';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 2) ? (@ == true)';
+ ?column?
+----------
+ true
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*] > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '($[*].a > 3).type()';
+ ?column?
+-----------
+ "boolean"
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict ($[*].a > 3).type()';
+ ?column?
+----------
+ "null"
+(1 row)
+
+select jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]' @* 'strict $[*].size()';
+ERROR: SQL/JSON array not found
+select jsonb '[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 jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].floor()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling()';
+ ?column?
+----------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs()';
+ ?column?
+----------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb '[0, 1, -2, -3.4, 5.6]' @* '$[*].ceiling().abs().type()';
+ ?column?
+----------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb '[{},1]' @* '$[*].keyvalue()';
+ERROR: SQL/JSON object not found
+select jsonb '{}' @* '$.keyvalue()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}' @* '$.keyvalue()';
+ ?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?
+-----------------------------------------------
+ {"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?
+-----------------------------------------------
+ {"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()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb 'true' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '[]' @* '$.double()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '{}' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '1.23' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23"' @* '$.double()';
+ ?column?
+----------
+ 1.23
+(1 row)
+
+select jsonb '"1.23aaa"' @* '$.double()';
+ERROR: Non-numeric SQL/JSON item
+select jsonb '["", "a", "abc", "abcabc"]' @* '$[*] ? (@ starts with "abc")';
+ ?column?
+----------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb '["", "a", "abc", "abcabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb '["", "a", "abd", "abdabc"]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? (@[*] starts with "abc")';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '["abc", "abcabc", null, 1]' @* 'strict $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb '[[null, 1, "abc", "abcabc"]]' @* 'lax $ ? (@[*] starts with "abc")';
+ ?column?
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb '[[null, 1, "abd", "abdabc"]]' @* 'lax $ ? ((@[*] starts with "abc") is unknown)';
+ ?column?
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb '[null, 1, "abd", "abdabc"]' @* 'lax $[*] ? ((@ starts with "abc") is unknown)';
+ ?column?
+----------
+ null
+ 1
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c")';
+ ?column?
+----------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")';
+ ?column?
+----------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb 'null' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb 'true' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '1' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '[]' @* '$.datetime()';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '[]' @* 'strict $.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '{}' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '""' @* '$.datetime()';
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy").type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"10-03-2017 12:34"' @* ' $.datetime("dd-mm-yyyy HH24:MI").type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+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 jsonb '"12:34:56"' @* '$.datetime("HH24:MI:SS").type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+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 jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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-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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy HH24:MI")';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:00"
+(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"
+(1 row)
+
+select jsonb '"10-03-2017 12:34 +05"' @* '$.datetime("dd-mm-yyyy HH24:MI TZH")';
+ ?column?
+-----------------------------
+ "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-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-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-10T12:34:00-05:20"
+(1 row)
+
+select jsonb '"12:34"' @* '$.datetime("HH24:MI")';
+ ?column?
+------------
+ "12:34:00"
+(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"
+(1 row)
+
+select jsonb '"12:34 +05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb '"12:34 -05"' @* '$.datetime("HH24:MI TZH")';
+ ?column?
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb '"12:34 +05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+ ?column?
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb '"2017-03-10"' @* '$.datetime().type()';
+ ?column?
+----------
+ "date"
+(1 row)
+
+select jsonb '"2017-03-10"' @* '$.datetime()';
+ ?column?
+--------------
+ "2017-03-10"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime().type()';
+ ?column?
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56"' @* '$.datetime()';
+ ?column?
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '"2017-03-10 12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime().type()';
+ ?column?
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb '"12:34:56"' @* '$.datetime()';
+ ?column?
+------------
+ "12:34:56"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime().type()';
+ ?column?
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb '"12:34:56 +3:10"' @* '$.datetime()';
+ ?column?
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+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-10T03:00:00+03:00"
+(3 rows)
+
+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"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+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-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+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 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 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"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+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 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"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+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"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+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-10T13:35:00+01:00"
+(2 rows)
+
+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"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+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-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb
+ '["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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb '[{"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';
+ ?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?
+----------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..193fc68
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5..177e031 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 json_jsonpath jsonb_jsonpath
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0c10c71..fa6a1d2 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -156,6 +156,9 @@ test: advisory_lock
test: json
test: jsonb
test: json_encoding
+test: jsonpath
+test: json_jsonpath
+test: jsonb_jsonpath
test: indirect_toast
test: equivclass
test: plancache
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
new file mode 100644
index 0000000..824f510
--- /dev/null
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -0,0 +1,379 @@
+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 $.**{0}';
+select json '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+select json '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+
+select json '{"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"' @* '$.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")';
+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"' @* '$.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")';
+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';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
new file mode 100644
index 0000000..43f34ef
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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)');
+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 $.**{0}';
+select jsonb '{"a": {"b": 1}}' @* 'lax $.**{0 to last}';
+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 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 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 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 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 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))';
+select 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);
+
+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)';
+
+-- 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';
+select jsonb '{"a": [2, 3, 4]}' @* 'lax -$.a';
+-- should fail
+select jsonb '{"a": [1, 2]}' @* 'lax $.a * 3';
+
+-- extension: boolean expressions
+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 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 '{"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 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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+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")';
+select jsonb '"12:34 -05:20"' @* '$.datetime("HH24:MI TZH:TZM")';
+
+set time zone default;
+
+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 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 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 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 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 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';
+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)';
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..8a3ea42
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1..0738c37 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 e4bb9d2..c46bae7 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
--
2.7.4
0003-Remove-PG_TRY-in-jsonpath-arithmetics-v23.patch (text/x-patch, 28.3K) download
From 95d8fe461af483fd5c23de4d79d7a8a9d1873ab7 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:21 +0300
Subject: [PATCH 03/13] remove PG_TRY in jsonpath arithmetics
---
src/backend/utils/adt/float.c | 48 +++---
src/backend/utils/adt/jsonpath_exec.c | 116 +++++--------
src/backend/utils/adt/numeric.c | 302 ++++++++++++++++++++++------------
src/include/utils/elog.h | 19 +++
src/include/utils/float.h | 7 +-
src/include/utils/numeric.h | 9 +
6 files changed, 305 insertions(+), 196 deletions(-)
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 117ded8..82da39e 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -293,7 +293,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
@@ -311,8 +311,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;
@@ -326,10 +326,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);
@@ -402,17 +405,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
@@ -435,10 +442,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 ccfd40e..9f0b20d 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"
@@ -914,7 +915,6 @@ static JsonPathExecResult
executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, JsonValueList *found)
{
- MemoryContext mcxt = CurrentMemoryContext;
JsonPathExecResult jper;
JsonPathItem elem;
JsonValueList lseq = { 0 };
@@ -923,11 +923,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);
@@ -966,25 +965,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);
@@ -992,33 +988,15 @@ executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
}
- /*
- * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
- * function called inside performs data modification.
- */
- 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);
}
@@ -2022,57 +2000,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);
- /*
- * It is safe to use here PG_TRY/PG_CATCH without subtransaction
- * because no function called inside performs data modification.
- */
- 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
{
- if (ERRCODE_TO_CATEGORY(geterrcode()) !=
- ERRCODE_DATA_EXCEPTION)
- PG_RE_THROW();
-
- FlushErrorState();
- MemoryContextSwitchTo(mcxt);
res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ break;
}
- PG_END_TRY();
- if (res == jperOk)
+ if (edata)
+ {
+ if (ERRCODE_TO_CATEGORY(edata->sqlerrcode) !=
+ ERRCODE_DATA_EXCEPTION)
+ ThrowErrorData(edata);
+
+ FreeErrorData(edata);
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ 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 45cd1a0..653ac82 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,17 @@ 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.
+ *
+ * If edata is non-NULL then when numeric-related error occurs error info
+ * should be placed into *edata (not thrown) and NULL is returned.
*/
static Numeric
-make_result(const NumericVar *var)
+make_result_safe(const NumericVar *var, ErrorData **edata)
{
Numeric result;
NumericDigit *digits = var->digits;
@@ -6166,14 +6240,27 @@ 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;
}
+/*
+ * make_result() -
+ *
+ * Same as make_result_safe(), but numeric-related errors are always thrown.
+ */
+static inline Numeric
+make_result(const NumericVar *var)
+{
+ return make_result_safe(var, NULL);
+}
/*
* apply_typmod() -
@@ -7051,7 +7138,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 +7163,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 +7789,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 +7802,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 +8458,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 648eedf..6d584eb 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 0f82a25..82f7289 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 9109cff..1aecca0 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_ */
--
2.7.4
0004-Jsonpath-docs-v23.patch (text/x-patch, 40.1K) download
From a186ae0a9b92bafb0c43bcc2ebad4137e270c4e5 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:21 +0300
Subject: [PATCH 04/13] Jsonpath docs
---
doc/src/sgml/biblio.sgml | 11 +
doc/src/sgml/func.sgml | 693 ++++++++++++++++++++++++++++++++++++++++++++++-
doc/src/sgml/json.sgml | 250 ++++++++++++++++-
3 files changed, 945 insertions(+), 9 deletions(-)
diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 4953024..f06305d 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d1febaf..c36961f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,661 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Enclosing the path specification into square brackets
+ <literal>[]</literal> automatically wraps the path evaluation
+ result into an array.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>Array of objects containing two members ("key" and "value" of the SQL/JSON item)</entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20"}, {"key": "y", "value": 32}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-extra-op-table">
+ <title>Extended <type>jsonpath</type> Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>min()</literal></entry>
+ <entry>Minimum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.min()</literal></entry>
+ <entry><literal>0</literal></entry>
+ </row>
+ <row>
+ <entry><literal>max()</literal></entry>
+ <entry>Maximum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.max()</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>map()</literal></entry>
+ <entry>Calculate an expression by applying a given function
+ to each element of the json array
+ </entry>
+ <entry><literal>[1, 2, 0]</literal></entry>
+ <entry><literal>$.map(@ * 2)</literal></entry>
+ <entry><literal>[2, 4, 0]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>reduce()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[3, 5, 9]</literal></entry>
+ <entry><literal>$.reduce($1 + $2)</literal></entry>
+ <entry><literal>17</literal></entry>
+ </row>
+ <row>
+ <entry><literal>fold()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[2, 3, 4]</literal></entry>
+ <entry><literal>$.fold($1 * $2, 1)</literal></entry>
+ <entry><literal>24</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldl()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from left to right
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldl([$1, $2], [])</literal></entry>
+ <entry><literal>[[[[], 1], 2], 3]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldr()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from right to left
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldr([$2, $1], [])</literal></entry>
+ <entry><literal>[[[[], 3], 2], 1]</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11977,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11986,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11994,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +12002,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,17 +12010,55 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
+ <row>
+ <entry><literal>@*</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>setof json</type> or <type>setof jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @* '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><programlisting>
+3
+4
+5
+</programlisting></entry>
+ </row>
+ <row>
+ <entry><literal>@#</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get all JSON items returned by JSON path for the specified JSON value. If there is more than one item, they will be wrapped into an array.</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @# '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>[3, 4, 5]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Check whether JSON path returns any item for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@~</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Get JSON path predicate result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::json @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12824,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +12879,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa..2ba7520 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -536,6 +549,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
--
2.7.4
0005-Jsonpath-GIN-support-v23.patch (text/x-patch, 47.0K) download
From ebe852fe97a0e8d3d49625e37e5b8f7f360f5368 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:21 +0300
Subject: [PATCH 05/13] Jsonpath GIN support
---
doc/src/sgml/gin.sgml | 4 +
src/backend/utils/adt/jsonb_gin.c | 903 ++++++++++++++++++++++++++++---
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 +++
8 files changed, 1384 insertions(+), 76 deletions(-)
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..8c51e4e 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
<literal>?&</literal>
<literal>?|</literal>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry>
<entry>
<literal>@></literal>
+ <literal>@?</literal>
+ <literal>@~</literal>
</entry>
</row>
<row>
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index a7f73b6..c3231f5 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,120 @@ typedef struct PathHashStack
struct PathHashStack *parent;
} PathHashStack;
+/* Buffer for GIN entries */
+typedef struct GinEntries
+{
+ Datum *buf;
+ int count;
+ int allocated;
+} GinEntries;
+
+typedef enum GinJsonPathNodeType
+{
+ GIN_JSP_OR,
+ GIN_JSP_AND,
+ GIN_JSP_ENTRY
+} GinJsonPathNodeType;
+
+typedef struct GinJsonPathNode GinJsonPathNode;
+
+/* Node in jsonpath expression tree */
+struct GinJsonPathNode
+{
+ GinJsonPathNodeType type;
+ union
+ {
+ int nargs; /* valid for OR and AND nodes */
+ int entryIndex; /* index in GinEntries array, valid for
+ * ENTRY nodes after entries output */
+ Datum entryDatum; /* path hash or key name/scalar, valid
+ * for ENTRY nodes before entries output */
+ } val;
+ GinJsonPathNode *args[FLEXIBLE_ARRAY_MEMBER]; /* valid for OR and AND nodes */
+};
+
+/*
+ * Single entry in the extracted json path (used for jsonb_ops only).
+ * Path entry can be one of:
+ * .key (key name is stored in 'entry' field)
+ * .*
+ * .**
+ * [index]
+ * [*]
+ * Entry type is stored in 'type' field.
+ */
+typedef struct GinJsonPathEntry
+{
+ struct GinJsonPathEntry *parent;
+ Datum entry; /* key name or NULL */
+ JsonPathItemType type;
+} GinJsonPathEntry;
+
+/* GIN representation of the extracted json path */
+typedef union GinJsonPath
+{
+ GinJsonPathEntry *entries; /* list of path entries (jsonb_ops) */
+ uint32 hash; /* hash of the path (jsonb_path_ops) */
+} GinJsonPath;
+
+typedef struct GinJsonPathContext GinJsonPathContext;
+
+/* Add entry to the extracted json path */
+typedef bool (*GinAddPathEntryFunc)(GinJsonPath *path, JsonPathItem *jsp);
+typedef List *(*GinExtractPathNodesFunc)(GinJsonPathContext *cxt,
+ GinJsonPath path, JsonbValue *scalar,
+ List *nodes);
+
+/* Context for jsonpath entries extraction */
+struct GinJsonPathContext
+{
+ GinAddPathEntryFunc add_path_entry;
+ GinExtractPathNodesFunc extract_path_nodes;
+ bool lax;
+};
+
static Datum make_text_key(char flag, const char *str, int len);
static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key);
+static GinJsonPathNode *extract_jsp_bool_expr(GinJsonPathContext *cxt,
+ GinJsonPath path, JsonPathItem *jsp, bool not);
+
+
+/* Init GIN entry buffer. */
+static void
+init_entries(GinEntries *entries, int preallocated)
+{
+ entries->allocated = preallocated;
+ entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL;
+ entries->count = 0;
+}
+
+/* Add GIN entry to the buffer. */
+static int
+add_entry(GinEntries *entries, Datum entry)
+{
+ int id = entries->count;
+
+ if (entries->count >= entries->allocated)
+ {
+ if (entries->allocated)
+ {
+ entries->allocated *= 2;
+ entries->buf = repalloc(entries->buf,
+ sizeof(Datum) * entries->allocated);
+ }
+ else
+ {
+ entries->allocated = 8;
+ entries->buf = palloc(sizeof(Datum) * entries->allocated);
+ }
+ }
+
+ entries->buf[entries->count++] = entry;
+
+ return id;
+}
+
/*
*
* jsonb_ops GIN opclass support functions
@@ -68,12 +181,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 +195,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
/* Otherwise, use 2 * root count as initial estimate of result size */
- entries = (Datum *) palloc(sizeof(Datum) * total);
+ init_entries(&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);
+ add_entry(&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));
+ add_entry(&entries, make_scalar_key(&v, v.type == jbvString));
break;
case WJB_VALUE:
- entries[i++] = make_scalar_key(&v, false);
+ add_entry(&entries, make_scalar_key(&v, false));
break;
default:
/* we can ignore structural items */
@@ -114,9 +219,575 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
}
}
- *nentries = i;
+ *nentries = entries.count;
- PG_RETURN_POINTER(entries);
+ PG_RETURN_POINTER(entries.buf);
+}
+
+/* Append key name to the path (jsonb_ops). */
+static bool
+jsonb_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+ GinJsonPathEntry *pentry;
+ Datum entry;
+
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ path->entries = NULL; /* reset path */
+ return true;
+
+ case jpiKey:
+ {
+ int len;
+ char *key = jspGetString(jsp, &len);
+
+ entry = make_text_key(JGINFLAG_KEY, key, len);
+ break;
+ }
+
+ case jpiAny:
+ case jpiAnyKey:
+ case jpiAnyArray:
+ case jpiIndexArray:
+ entry = PointerGetDatum(NULL);
+ break;
+
+ default:
+ /* other path items like item methods are not supported */
+ return false;
+ }
+
+ pentry = palloc(sizeof(*pentry));
+
+ pentry->type = jsp->type;
+ pentry->entry = entry;
+ pentry->parent = path->entries;
+
+ path->entries = pentry;
+
+ return true;
+}
+
+/* Combine existing path hash with next key hash (jsonb_path_ops). */
+static bool
+jsonb_path_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+ switch (jsp->type)
+ {
+ case jpiRoot:
+ path->hash = 0; /* reset path hash */
+ return true;
+
+ case jpiKey:
+ {
+ JsonbValue jbv;
+
+ jbv.type = jbvString;
+ jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len);
+
+ JsonbHashScalarValue(&jbv, &path->hash);
+ return true;
+ }
+
+ case jpiIndexArray:
+ case jpiAnyArray:
+ return true; /* path hash is unchanged */
+
+ default:
+ /* other items (wildcard paths, item methods) are not supported */
+ return false;
+ }
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node(Datum entry)
+{
+ GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args));
+
+ node->type = GIN_JSP_ENTRY;
+ node->val.entryDatum = entry;
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
+{
+ return make_jsp_entry_node(make_scalar_key(scalar, iskey));
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node(GinJsonPathNodeType type, int nargs)
+{
+ GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args) +
+ sizeof(node->args[0]) * nargs);
+
+ node->type = type;
+ node->val.nargs = nargs;
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_args(GinJsonPathNodeType type, List *args)
+{
+ GinJsonPathNode *node = make_jsp_expr_node(type, list_length(args));
+ ListCell *lc;
+ int i = 0;
+
+ foreach(lc, args)
+ node->args[i++] = lfirst(lc);
+
+ return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_binary(GinJsonPathNodeType type,
+ GinJsonPathNode *arg1, GinJsonPathNode *arg2)
+{
+ GinJsonPathNode *node = make_jsp_expr_node(type, 2);
+
+ node->args[0] = arg1;
+ node->args[1] = arg2;
+
+ return node;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_ops). */
+static List *
+jsonb_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonbValue *scalar, List *nodes)
+{
+ GinJsonPathEntry *pentry;
+
+ /* append path entry nodes */
+ for (pentry = path.entries; pentry; pentry = pentry->parent)
+ {
+ if (pentry->type == jpiKey) /* only keys are indexed */
+ nodes = lappend(nodes, make_jsp_entry_node(pentry->entry));
+ }
+
+ if (scalar)
+ {
+ /* Append scalar node for equality queries. */
+ GinJsonPathNode *node;
+
+ if (scalar->type == jbvString)
+ {
+ GinJsonPathEntry *last = path.entries;
+ GinTernaryValue array_access;
+
+ /*
+ * 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 (cxt->lax)
+ array_access = GIN_MAYBE;
+ else if (!last) /* root ($) */
+ array_access = GIN_FALSE;
+ else if (last->type == jpiAnyArray || last->type == jpiIndexArray)
+ array_access = GIN_TRUE;
+ else if (last->type == jpiAny)
+ array_access = GIN_MAYBE;
+ else
+ array_access = GIN_FALSE;
+
+ if (array_access == GIN_MAYBE)
+ {
+ GinJsonPathNode *n1 = make_jsp_entry_node_scalar(scalar, true);
+ GinJsonPathNode *n2 = make_jsp_entry_node_scalar(scalar, false);
+
+ node = make_jsp_expr_node_binary(GIN_JSP_OR, n1, n2);
+ }
+ else
+ {
+ node = make_jsp_entry_node_scalar(scalar,
+ array_access == GIN_TRUE);
+ }
+ }
+ else
+ {
+ node = make_jsp_entry_node_scalar(scalar, false);
+ }
+
+ nodes = lappend(nodes, node);
+ }
+
+ return nodes;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_path_ops). */
+static List *
+jsonb_path_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonbValue *scalar, List *nodes)
+{
+ if (scalar)
+ {
+ /* append path hash node for equality queries */
+ uint32 hash = path.hash;
+
+ JsonbHashScalarValue(scalar, &hash);
+
+ return lappend(nodes,
+ make_jsp_entry_node(UInt32GetDatum(hash)));
+ }
+ else
+ {
+ /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */
+ return nodes;
+ }
+}
+
+/*
+ * Extract a list of expression nodes that need to be AND-ed by the caller.
+ * Extracted expression is 'path == scalar' if 'scalar' is non-NULL, and
+ * 'EXISTS(path)' otherwise.
+ */
+static List *
+extract_jsp_path_expr_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, JsonbValue *scalar)
+{
+ JsonPathItem next;
+ List *nodes = NIL;
+
+ for (;;)
+ {
+ switch (jsp->type)
+ {
+ case jpiCurrent:
+ break;
+
+ case jpiFilter:
+ {
+ JsonPathItem arg;
+ GinJsonPathNode *filter;
+
+ jspGetArg(jsp, &arg);
+
+ filter = extract_jsp_bool_expr(cxt, path, &arg, false);
+
+ if (filter)
+ nodes = lappend(nodes, filter);
+
+ break;
+ }
+
+ default:
+ if (!cxt->add_path_entry(&path, jsp))
+ /*
+ * Path is not supported by the index opclass, return only
+ * the extracted filter nodes.
+ */
+ return nodes;
+ break;
+ }
+
+ if (!jspGetNext(jsp, &next))
+ break;
+
+ jsp = &next;
+ }
+
+ /*
+ * Append nodes from the path expression itself to the already extracted
+ * list of filter nodes.
+ */
+ return cxt->extract_path_nodes(cxt, path, scalar, nodes);
+}
+
+/*
+ * Extract an expression node from one of following jsonpath path expressions:
+ * EXISTS(jsp) (when 'scalar' is NULL)
+ * jsp == scalar (when 'scalar' is not NULL).
+ *
+ * The current path (@) is passed in 'path'.
+ */
+static GinJsonPathNode *
+extract_jsp_path_expr(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, JsonbValue *scalar)
+{
+ /* extract a list of nodes to be AND-ed */
+ List *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
+
+ if (list_length(nodes) <= 0)
+ /* no nodes were extracted => full scan is needed for this path */
+ return NULL;
+
+ if (list_length(nodes) == 1)
+ return linitial(nodes); /* avoid extra AND-node */
+
+ /* construct AND-node for path with filters */
+ return make_jsp_expr_node_args(GIN_JSP_AND, nodes);
+}
+
+/* Recursively extract nodes from the boolean jsonpath expression. */
+static GinJsonPathNode *
+extract_jsp_bool_expr(GinJsonPathContext *cxt, GinJsonPath path,
+ JsonPathItem *jsp, bool not)
+{
+ check_stack_depth();
+
+ switch (jsp->type)
+ {
+ case jpiAnd: /* expr && expr */
+ case jpiOr: /* expr || expr */
+ {
+ JsonPathItem arg;
+ GinJsonPathNode *larg;
+ GinJsonPathNode *rarg;
+ GinJsonPathNodeType type;
+
+ jspGetLeftArg(jsp, &arg);
+ larg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+ jspGetRightArg(jsp, &arg);
+ rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+ if (!larg || !rarg)
+ {
+ if (jsp->type == jpiOr)
+ return NULL;
+
+ return larg ? larg : rarg;
+ }
+
+ type = not ^ (jsp->type == jpiAnd) ? GIN_JSP_AND : GIN_JSP_OR;
+
+ return make_jsp_expr_node_binary(type, larg, rarg);
+ }
+
+ case jpiNot: /* !expr */
+ {
+ JsonPathItem arg;
+
+ jspGetArg(jsp, &arg);
+
+ /* extract child expression inverting 'not' flag */
+ return extract_jsp_bool_expr(cxt, path, &arg, !not);
+ }
+
+ case jpiExists: /* EXISTS(path) */
+ {
+ JsonPathItem arg;
+
+ if (not)
+ return NULL; /* NOT EXISTS is not supported */
+
+ jspGetArg(jsp, &arg);
+
+ return extract_jsp_path_expr(cxt, path, &arg, NULL);
+ }
+
+ case jpiNotEqual:
+ /*
+ * 'not' == true case is not supported here because
+ * '!(path != scalar)' is not equivalent to 'path == scalar' in the
+ * general case because of sequence comparison semantics:
+ * 'path == scalar' === 'EXISTS (path, @ == scalar)',
+ * '!(path != scalar)' === 'FOR_ALL(path, @ == scalar)'.
+ * So, we should translate '!(path != scalar)' into GIN query
+ * 'path == scalar || EMPTY(path)', but 'EMPTY(path)' queries
+ * are not supported by the both jsonb opclasses. However in strict
+ * mode we could omit 'EMPTY(path)' part if the path can return
+ * exactly one item (it does not contain wildcard accessors or
+ * item methods like .keyvalue() etc.).
+ */
+ return NULL;
+
+ case jpiEqual: /* path == scalar */
+ {
+ JsonPathItem left_item;
+ JsonPathItem right_item;
+ JsonPathItem *path_item;
+ JsonPathItem *scalar_item;
+ JsonbValue scalar;
+
+
+
+ if (not)
+ return NULL;
+
+ jspGetLeftArg(jsp, &left_item);
+ jspGetRightArg(jsp, &right_item);
+
+ if (jspIsScalar(left_item.type))
+ {
+ scalar_item = &left_item;
+ path_item = &right_item;
+ }
+ else if (jspIsScalar(right_item.type))
+ {
+ scalar_item = &right_item;
+ path_item = &left_item;
+ }
+ else
+ return NULL; /* at least one operand should be a scalar */
+
+ switch (scalar_item->type)
+ {
+ case jpiNull:
+ scalar.type = jbvNull;
+ break;
+ case jpiBool:
+ scalar.type = jbvBool;
+ scalar.val.boolean = !!*scalar_item->content.value.data;
+ break;
+ case jpiNumeric:
+ scalar.type = jbvNumeric;
+ scalar.val.numeric =
+ (Numeric) scalar_item->content.value.data;
+ break;
+ case jpiString:
+ scalar.type = jbvString;
+ scalar.val.string.val = scalar_item->content.value.data;
+ scalar.val.string.len =
+ scalar_item->content.value.datalen;
+ break;
+ default:
+ elog(ERROR, "invalid scalar jsonpath item type: %d",
+ scalar_item->type);
+ return NULL;
+ }
+
+ return extract_jsp_path_expr(cxt, path, path_item, &scalar);
+ }
+
+ default:
+ return NULL; /* not a boolean expression */
+ }
+}
+
+/* Recursively emit all GIN entries found in the node tree */
+static void
+emit_jsp_entries(GinJsonPathNode *node, GinEntries *entries)
+{
+ check_stack_depth();
+
+ switch (node->type)
+ {
+ case GIN_JSP_ENTRY:
+ /* replace datum with its index in the array */
+ node->val.entryIndex = add_entry(entries, node->val.entryDatum);
+ break;
+
+ case GIN_JSP_OR:
+ case GIN_JSP_AND:
+ {
+ int i;
+
+ for (i = 0; i < node->val.nargs; i++)
+ emit_jsp_entries(node->args[i], entries);
+
+ break;
+ }
+ }
+}
+
+/*
+ * Recursively extract GIN entries from jsonpath query.
+ * Root expression node is put into (*extra_data)[0].
+ */
+static Datum *
+extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+ int32 *nentries, Pointer **extra_data)
+{
+ GinJsonPathContext cxt;
+ JsonPathItem root;
+ GinJsonPathNode *node;
+ GinJsonPath path = { 0 };
+ GinEntries entries = { 0 };
+
+ cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+ if (pathOps)
+ {
+ cxt.add_path_entry = jsonb_path_ops__add_path_entry;
+ cxt.extract_path_nodes = jsonb_path_ops__extract_path_nodes;
+ }
+ else
+ {
+ cxt.add_path_entry = jsonb_ops__add_path_entry;
+ cxt.extract_path_nodes = jsonb_ops__extract_path_nodes;
+ }
+
+ jspInit(&root, jp);
+
+ node = strat == JsonbJsonpathExistsStrategyNumber
+ ? extract_jsp_path_expr(&cxt, path, &root, NULL)
+ : extract_jsp_bool_expr(&cxt, path, &root, false);
+
+ if (!node)
+ {
+ *nentries = 0;
+ return NULL;
+ }
+
+ emit_jsp_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;
+}
+
+/*
+ * Recursively execute jsonpath expression.
+ * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag.
+ */
+static GinTernaryValue
+execute_jsp_expr(GinJsonPathNode *node, void *check, bool ternary)
+{
+ GinTernaryValue res;
+ GinTernaryValue v;
+ int i;
+
+ switch (node->type)
+ {
+ case GIN_JSP_AND:
+ res = GIN_TRUE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = execute_jsp_expr(node->args[i], check, ternary);
+ if (v == GIN_FALSE)
+ return GIN_FALSE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case GIN_JSP_OR:
+ res = GIN_FALSE;
+ for (i = 0; i < node->val.nargs; i++)
+ {
+ v = execute_jsp_expr(node->args[i], check, ternary);
+ if (v == GIN_TRUE)
+ return GIN_TRUE;
+ else if (v == GIN_MAYBE)
+ res = GIN_MAYBE;
+ }
+ return res;
+
+ case GIN_JSP_ENTRY:
+ {
+ 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 +852,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 = extract_jsp_query(jp, strategy, false, nentries,
+ extra_data);
+
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
else
{
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -199,7 +882,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 +939,22 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ *recheck = true;
+
+ if (nkeys <= 0)
+ {
+ res = true;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ false) != GIN_FALSE;
+ }
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -270,8 +969,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 +1006,20 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
}
}
}
+ else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+ strategy == JsonbJsonpathExistsStrategyNumber)
+ {
+ if (nkeys <= 0)
+ {
+ res = GIN_MAYBE;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ true);
+ }
+ }
else
elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -331,14 +1043,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 +1059,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);
+ init_entries(&entries, 2 * total);
/* We keep a stack of partial hashes corresponding to parent key levels */
tail.parent = NULL;
@@ -361,13 +1072,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 +1102,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);
+ add_entry(&entries, UInt32GetDatum(stack->hash));
/* reset hash for next key, value, or sub-object */
stack->hash = stack->parent->hash;
break;
@@ -419,9 +1123,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 +1136,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)));
+
+ /* ... 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);
- /* 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)));
+ entries = extract_jsp_query(jp, strategy, true, nentries,
+ extra_data);
- /* ... although "contains {}" requires a full index scan */
- if (*nentries == 0)
- *searchMode = GIN_SEARCH_MODE_ALL;
+ if (!entries)
+ *searchMode = GIN_SEARCH_MODE_ALL;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
+ entries = NULL;
+ }
PG_RETURN_POINTER(entries);
}
@@ -456,32 +1177,49 @@ 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)
+ {
+ *recheck = true;
+
+ if (nkeys <= 0)
+ {
+ res = true;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+ false) != GIN_FALSE;
}
}
+ else
+ elog(ERROR, "unrecognized strategy number: %d", strategy);
PG_RETURN_BOOL(res);
}
@@ -494,27 +1232,42 @@ 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)
+ {
+ if (nkeys <= 0)
+ {
+ res = GIN_MAYBE;
+ }
+ else
+ {
+ Assert(extra_data && extra_data[0]);
+ res = execute_jsp_expr((GinJsonPathNode *) 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 0ab95d8..052f3fe 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1468,11 +1468,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 06ae0b0..8664265 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 5d16131..b3cf4c2 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 0ac47fc..8c4ad0e 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2731,6 +2731,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}';
@@ -2806,6 +2914,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';
@@ -2956,6 +3254,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 7328095..12772ac 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1906,6 +1906,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ?
2742 | 10 | ?|
2742 | 11 | ?&
+ 2742 | 15 | @?
+ 2742 | 16 | @~
3580 | 1 | <
3580 | 1 | <<
3580 | 2 | &<
@@ -1971,7 +1973,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 6cbdfe4..d6dcd73 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -740,6 +740,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;
@@ -758,6 +776,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';
@@ -807,6 +858,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;
--
2.7.4
0006-Jsonpath-syntax-extensions-v23.patch (text/x-patch, 48.4K) download
From b8c37dc01daed342f0f879e307d285bcc36ef703 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Sat, 12 Jan 2019 01:11:22 +0300
Subject: [PATCH 06/13] Jsonpath syntax extensions
---
src/backend/utils/adt/jsonpath.c | 153 ++++++++-
src/backend/utils/adt/jsonpath_exec.c | 464 ++++++++++++++++++++++-----
src/backend/utils/adt/jsonpath_gram.y | 55 +++-
src/include/utils/jsonpath.h | 28 ++
src/test/regress/expected/json_jsonpath.out | 228 ++++++++++++-
src/test/regress/expected/jsonb_jsonpath.out | 262 ++++++++++++++-
src/test/regress/expected/jsonpath.out | 66 ++++
src/test/regress/sql/json_jsonpath.sql | 47 +++
src/test/regress/sql/jsonb_jsonpath.sql | 58 +++-
src/test/regress/sql/jsonpath.sql | 14 +
10 files changed, 1284 insertions(+), 91 deletions(-)
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 11d457d..456db2e 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -136,12 +136,15 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiPlus:
case jpiMinus:
case jpiExists:
+ case jpiArray:
{
- int32 arg;
+ int32 arg = item->value.arg ? buf->len : 0;
- arg = buf->len;
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+ if (!item->value.arg)
+ break;
+
chld = flattenJsonPathParseItem(buf, item->value.arg,
nestingLevel + argNestingLevel,
insideArraySubscript);
@@ -218,6 +221,61 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
case jpiDouble:
case jpiKeyValue:
break;
+ case jpiSequence:
+ {
+ int32 nelems = list_length(item->value.sequence.elems);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * nelems);
+
+ foreach(lc, item->value.sequence.elems)
+ {
+ int32 elempos =
+ flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
+ insideArraySubscript);
+
+ *(int32 *) &buf->data[offset] = elempos - pos;
+ offset += sizeof(int32);
+ }
+ }
+ break;
+ case jpiObject:
+ {
+ int32 nfields = list_length(item->value.object.fields);
+ ListCell *lc;
+ int offset;
+
+ appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
+
+ offset = buf->len;
+
+ appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
+
+ foreach(lc, item->value.object.fields)
+ {
+ JsonPathParseItem *field = lfirst(lc);
+ int32 keypos =
+ flattenJsonPathParseItem(buf, field->value.args.left,
+ nestingLevel,
+ insideArraySubscript);
+ int32 valpos =
+ flattenJsonPathParseItem(buf, field->value.args.right,
+ nestingLevel,
+ insideArraySubscript);
+ int32 *ppos = (int32 *) &buf->data[offset];
+
+ ppos[0] = keypos - pos;
+ ppos[1] = valpos - pos;
+
+ offset += 2 * sizeof(int32);
+ }
+ }
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
}
@@ -305,6 +363,8 @@ operationPriority(JsonPathItemType op)
{
switch (op)
{
+ case jpiSequence:
+ return -1;
case jpiOr:
return 0;
case jpiAnd:
@@ -494,12 +554,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
if (i)
appendStringInfoChar(buf, ',');
- printJsonPathItem(buf, &from, false, false);
+ printJsonPathItem(buf, &from, false, from.type == jpiSequence);
if (range)
{
appendBinaryStringInfo(buf, " to ", 4);
- printJsonPathItem(buf, &to, false, false);
+ printJsonPathItem(buf, &to, false, to.type == jpiSequence);
}
}
appendStringInfoChar(buf, ']');
@@ -563,6 +623,54 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
case jpiKeyValue:
appendBinaryStringInfo(buf, ".keyvalue()", 11);
break;
+ case jpiSequence:
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, '(');
+
+ for (i = 0; i < v->content.sequence.nelems; i++)
+ {
+ JsonPathItem elem;
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ jspGetSequenceElement(v, i, &elem);
+
+ printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
+ }
+
+ if (printBracketes || jspHasNext(v))
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiArray:
+ appendStringInfoChar(buf, '[');
+ if (v->content.arg)
+ {
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiObject:
+ appendStringInfoChar(buf, '{');
+
+ for (i = 0; i < v->content.object.nfields; i++)
+ {
+ JsonPathItem key;
+ JsonPathItem val;
+
+ jspGetObjectField(v, i, &key, &val);
+
+ if (i)
+ appendBinaryStringInfo(buf, ", ", 2);
+
+ printJsonPathItem(buf, &key, false, false);
+ appendBinaryStringInfo(buf, ": ", 2);
+ printJsonPathItem(buf, &val, false, val.type == jpiSequence);
+ }
+
+ appendStringInfoChar(buf, '}');
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -585,7 +693,7 @@ jsonpath_out(PG_FUNCTION_ARGS)
appendBinaryStringInfo(&buf, "strict ", 7);
jspInit(&v, in);
- printJsonPathItem(&buf, &v, false, true);
+ printJsonPathItem(&buf, &v, false, v.type != jpiSequence);
PG_RETURN_CSTRING(buf.data);
}
@@ -688,6 +796,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
case jpiPlus:
case jpiMinus:
case jpiFilter:
+ case jpiArray:
read_int32(v->content.arg, base, pos);
break;
case jpiIndexArray:
@@ -699,6 +808,16 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
read_int32(v->content.anybounds.first, base, pos);
read_int32(v->content.anybounds.last, base, pos);
break;
+ case jpiSequence:
+ read_int32(v->content.sequence.nelems, base, pos);
+ read_int32_n(v->content.sequence.elems, base, pos,
+ v->content.sequence.nelems);
+ break;
+ case jpiObject:
+ read_int32(v->content.object.nfields, base, pos);
+ read_int32_n(v->content.object.fields, base, pos,
+ v->content.object.nfields * 2);
+ break;
default:
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
}
@@ -713,7 +832,8 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
v->type == jpiIsUnknown ||
v->type == jpiExists ||
v->type == jpiPlus ||
- v->type == jpiMinus
+ v->type == jpiMinus ||
+ v->type == jpiArray
);
jspInitByBuffer(a, v->base, v->content.arg);
@@ -765,7 +885,10 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
v->type == jpiDouble ||
v->type == jpiDatetime ||
v->type == jpiKeyValue ||
- v->type == jpiStartsWith
+ v->type == jpiStartsWith ||
+ v->type == jpiSequence ||
+ v->type == jpiArray ||
+ v->type == jpiObject
);
if (a)
@@ -869,3 +992,19 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
return true;
}
+
+void
+jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
+{
+ Assert(v->type == jpiSequence);
+
+ jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
+}
+
+void
+jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
+{
+ Assert(v->type == jpiObject);
+ jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
+ jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
+}
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 9f0b20d..a1c01a7 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -80,6 +80,8 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+static inline JsonbValue *wrapItem(JsonbValue *jbv);
+
static inline JsonbValue *wrapItemsInArray(const JsonValueList *items);
@@ -1672,7 +1674,116 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
break;
case jpiIndexArray:
- if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
+ if (JsonbType(jb) == jbvObject)
+ {
+ int innermostArraySize = cxt->innermostArraySize;
+ int i;
+ JsonbValue bin;
+
+ if (jb->type != jbvBinary)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ cxt->innermostArraySize = 1;
+
+ for (i = 0; i < jsp->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ JsonbValue *key;
+ JsonbValue tmp;
+ JsonValueList keys = { 0 };
+ bool range = jspGetArraySubscript(jsp, &from, &to, i);
+
+ if (range)
+ {
+ int index_from;
+ int index_to;
+
+ if (!jspAutoWrap(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ res = getArrayIndex(cxt, &from, jb, &index_from);
+ if (jperIsError(res))
+ return res;
+
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+ if (jperIsError(res))
+ return res;
+
+ res = jperNotFound;
+
+ if (index_from <= 0 && index_to >= 0)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+
+ if (res == jperOk && !found)
+ break;
+
+ continue;
+ }
+
+ res = recursiveExecute(cxt, &from, jb, &keys);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&keys) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ key = JsonValueListHead(&keys);
+
+ if (JsonbType(key) == jbvScalar)
+ key = JsonbExtractScalar(key->val.binary.data, &tmp);
+
+ res = jperNotFound;
+
+ if (key->type == jbvNumeric && jspAutoWrap(cxt))
+ {
+ int index = DatumGetInt32(
+ DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(key->val.numeric),
+ Int32GetDatum(0))));
+
+ if (!index)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb,
+ found, true);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ }
+ else if (key->type == jbvString)
+ {
+ key = findJsonbValueFromContainer(jb->val.binary.data,
+ JB_FOBJECT, key);
+
+ if (key)
+ {
+ res = recursiveExecuteNext(cxt, jsp, NULL, key,
+ found, false);
+ if (jperIsError(res))
+ return res;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{
int innermostArraySize = cxt->innermostArraySize;
int i;
@@ -1773,9 +1884,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
cxt->innermostArraySize = innermostArraySize;
}
- else if (!jspIgnoreStructuralErrors(cxt))
+ else
{
- res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
+ if (jspAutoWrap(cxt))
+ res = recursiveExecuteNoUnwrap(cxt, jsp, wrapItem(jb),
+ found);
+ else if (!jspIgnoreStructuralErrors(cxt))
+ res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
}
break;
@@ -2053,7 +2168,6 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
{
JsonbValue jbvbuf;
Datum value;
- text *datetime;
Oid typid;
int32 typmod = -1;
int tz;
@@ -2064,99 +2178,130 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
- if (jb->type != jbvString)
- break;
+ if (jb->type == jbvNumeric && !jsp->content.args.left)
+ {
+ /* Standard extension: unix epoch to timestamptz */
+ MemoryContext mcxt = CurrentMemoryContext;
- datetime = cstring_to_text_with_len(jb->val.string.val,
- jb->val.string.len);
+ PG_TRY();
+ {
+ Datum unix_epoch =
+ DirectFunctionCall1(numeric_float8,
+ NumericGetDatum(jb->val.numeric));
+
+ value = DirectFunctionCall1(float8_timestamptz,
+ unix_epoch);
+ typid = TIMESTAMPTZOID;
+ tz = 0;
+ res = jperOk;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) !=
+ ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
- if (jsp->content.args.left)
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+ }
+ else if (jb->type == jbvString)
{
- text *template;
- char *template_str;
- int template_len;
- char *tzname = NULL;
+ text *datetime =
+ cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
- jspGetLeftArg(jsp, &elem);
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
- if (elem.type != jpiString)
- elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+ jspGetLeftArg(jsp, &elem);
- template_str = jspGetString(&elem, &template_len);
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
- if (jsp->content.args.right)
- {
- JsonValueList tzlist = { 0 };
- JsonPathExecResult tzres;
- JsonbValue *tzjbv;
+ template_str = jspGetString(&elem, &template_len);
- jspGetRightArg(jsp, &elem);
- tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
- &tzlist);
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
- if (jperIsError(tzres))
- return tzres;
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
- if (JsonValueListLength(&tzlist) != 1)
- break;
+ if (jperIsError(tzres))
+ return tzres;
- tzjbv = JsonValueListHead(&tzlist);
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
- if (tzjbv->type != jbvString)
- break;
+ tzjbv = JsonValueListHead(&tzlist);
- tzname = pnstrdup(tzjbv->val.string.val,
- tzjbv->val.string.len);
- }
+ if (tzjbv->type != jbvString)
+ break;
- template = cstring_to_text_with_len(template_str,
- template_len);
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
- if (tryToParseDatetime(template, datetime, tzname, false,
- &value, &typid, &typmod, &tz))
- res = jperOk;
+ template = cstring_to_text_with_len(template_str,
+ template_len);
- if (tzname)
- pfree(tzname);
- }
- else
- {
- /* Try to recognize one of ISO formats. */
- static const char *fmt_str[] =
- {
- "yyyy-mm-dd HH24:MI:SS TZH:TZM",
- "yyyy-mm-dd HH24:MI:SS TZH",
- "yyyy-mm-dd HH24:MI:SS",
- "yyyy-mm-dd",
- "HH24:MI:SS TZH:TZM",
- "HH24:MI:SS TZH",
- "HH24:MI:SS"
- };
- /* cache for format texts */
- static text *fmt_txt[lengthof(fmt_str)] = { 0 };
- int i;
-
- for (i = 0; i < lengthof(fmt_str); i++)
+ if (tryToParseDatetime(template, datetime, tzname,
+ false, &value, &typid, &typmod,
+ &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
{
- if (!fmt_txt[i])
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
{
- MemoryContext oldcxt =
- MemoryContextSwitchTo(TopMemoryContext);
-
- fmt_txt[i] = cstring_to_text(fmt_str[i]);
- MemoryContextSwitchTo(oldcxt);
- }
-
- if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
- &value, &typid, &typmod, &tz))
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
{
- res = jperOk;
- break;
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL,
+ true, &value, &typid,
+ &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
}
}
- }
- pfree(datetime);
+ pfree(datetime);
+ }
if (jperIsError(res))
break;
@@ -2286,6 +2431,133 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
}
}
break;
+ case jpiSequence:
+ {
+ JsonPathItem next;
+ bool hasNext = jspGetNext(jsp, &next);
+ JsonValueList list;
+ JsonValueList *plist = hasNext ? &list : found;
+ JsonValueListIterator it;
+ int i;
+
+ for (i = 0; i < jsp->content.sequence.nelems; i++)
+ {
+ JsonbValue *v;
+
+ if (hasNext)
+ memset(&list, 0, sizeof(list));
+
+ jspGetSequenceElement(jsp, i, &elem);
+ res = recursiveExecute(cxt, &elem, jb, plist);
+
+ if (jperIsError(res))
+ break;
+
+ if (!hasNext)
+ {
+ if (!found && res == jperOk)
+ break;
+ continue;
+ }
+
+ memset(&it, 0, sizeof(it));
+
+ while ((v = JsonValueListNext(&list, &it)))
+ {
+ res = recursiveExecute(cxt, &next, v, found);
+
+ if (jperIsError(res) || (!found && res == jperOk))
+ {
+ i = jsp->content.sequence.nelems;
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+ case jpiArray:
+ {
+ JsonValueList list = { 0 };
+
+ if (jsp->content.arg)
+ {
+ jspGetArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &list);
+
+ if (jperIsError(res))
+ break;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, NULL,
+ wrapItemsInArray(&list),
+ found, false);
+ }
+ break;
+ case jpiObject:
+ {
+ JsonbParseState *ps = NULL;
+ JsonbValue *obj;
+ int i;
+
+ pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
+
+ for (i = 0; i < jsp->content.object.nfields; i++)
+ {
+ JsonbValue *jbv;
+ JsonbValue jbvtmp;
+ JsonPathItem key;
+ JsonPathItem val;
+ JsonValueList key_list = { 0 };
+ JsonValueList val_list = { 0 };
+
+ jspGetObjectField(jsp, i, &key, &val);
+
+ recursiveExecute(cxt, &key, jb, &key_list);
+
+ if (JsonValueListLength(&key_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&key_list);
+
+ if (JsonbType(jbv) == jbvScalar)
+ jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
+
+ if (jbv->type != jbvString)
+ {
+ res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
+ break;
+ }
+
+ pushJsonbValue(&ps, WJB_KEY, jbv);
+
+ recursiveExecute(cxt, &val, jb, &val_list);
+
+ if (JsonValueListLength(&val_list) != 1)
+ {
+ res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+ break;
+ }
+
+ jbv = JsonValueListHead(&val_list);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &jbvtmp);
+
+ pushJsonbValue(&ps, WJB_VALUE, jbv);
+ }
+
+ if (jperIsError(res))
+ break;
+
+ obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
+
+ res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
+ }
+ break;
default:
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
}
@@ -2762,6 +3034,48 @@ jsonb_jsonpath_query_wrapped3(PG_FUNCTION_ARGS)
makePassingVars(PG_GETARG_JSONB_P(2)));
}
+/*
+ * Wrap a non-array SQL/JSON item into an array for applying array subscription
+ * path steps in lax mode.
+ */
+static inline 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);
+}
+
/* Construct a JSON array from the item list */
static inline JsonbValue *
wrapItemsInArray(const JsonValueList *items)
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 3856a06..be1d488 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -255,6 +255,26 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
return v;
}
+static JsonPathParseItem *
+makeItemSequence(List *elems)
+{
+ JsonPathParseItem *v = makeItemType(jpiSequence);
+
+ v->value.sequence.elems = elems;
+
+ return v;
+}
+
+static JsonPathParseItem *
+makeItemObject(List *fields)
+{
+ JsonPathParseItem *v = makeItemType(jpiObject);
+
+ v->value.object.fields = fields;
+
+ return v;
+}
+
%}
/* BISON Declarations */
@@ -288,9 +308,9 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%type <value> scalar_value path_primary expr array_accessor
any_path accessor_op key predicate delimited_predicate
index_elem starts_with_initial datetime_template opt_datetime_template
- expr_or_predicate
+ expr_or_predicate expr_or_seq expr_seq object_field
-%type <elems> accessor_expr
+%type <elems> accessor_expr expr_list object_field_list
%type <indexs> index_list
@@ -314,7 +334,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
%%
result:
- mode expr_or_predicate {
+ mode expr_or_seq {
*result = palloc(sizeof(JsonPathParseResult));
(*result)->expr = $2;
(*result)->lax = $1;
@@ -327,6 +347,20 @@ expr_or_predicate:
| predicate { $$ = $1; }
;
+expr_or_seq:
+ expr_or_predicate { $$ = $1; }
+ | expr_seq { $$ = $1; }
+ ;
+
+expr_seq:
+ expr_list { $$ = makeItemSequence($1); }
+ ;
+
+expr_list:
+ expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
+ | expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
+ ;
+
mode:
STRICT_P { $$ = false; }
| LAX_P { $$ = true; }
@@ -381,6 +415,21 @@ path_primary:
| '$' { $$ = makeItemType(jpiRoot); }
| '@' { $$ = makeItemType(jpiCurrent); }
| LAST_P { $$ = makeItemType(jpiLast); }
+ | '(' expr_seq ')' { $$ = $2; }
+ | '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
+ | '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
+ | '{' object_field_list '}' { $$ = makeItemObject($2); }
+ ;
+
+object_field_list:
+ /* EMPTY */ { $$ = NIL; }
+ | object_field { $$ = list_make1($1); }
+ | object_field_list ',' object_field { $$ = lappend($1, $3); }
+ ;
+
+object_field:
+ key_name ':' expr_or_predicate
+ { $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
;
accessor_expr:
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index b3cf4c2..3747985 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -84,6 +84,10 @@ typedef enum JsonPathItemType {
jpiLast, /* LAST array subscript */
jpiStartsWith, /* STARTS WITH predicate */
jpiLikeRegex, /* LIKE_REGEX predicate */
+ jpiSequence, /* sequence constructor: 'expr, ...' */
+ jpiArray, /* array constructor: '[expr, ...]' */
+ jpiObject, /* object constructor: '{ key : value, ... }' */
+ jpiObjectField, /* element of object constructor: 'key : value' */
} JsonPathItemType;
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -138,6 +142,19 @@ typedef struct JsonPathItem {
} anybounds;
struct {
+ int32 nelems;
+ int32 *elems;
+ } sequence;
+
+ struct {
+ int32 nfields;
+ struct {
+ int32 key;
+ int32 val;
+ } *fields;
+ } object;
+
+ struct {
char *data; /* for bool, numeric and string/key */
int32 datalen; /* filled only for string/key */
} value;
@@ -164,6 +181,9 @@ extern bool jspGetBool(JsonPathItem *v);
extern char * jspGetString(JsonPathItem *v, int32 *len);
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
JsonPathItem *to, int i);
+extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
+extern void jspGetObjectField(JsonPathItem *v, int i,
+ JsonPathItem *key, JsonPathItem *val);
/*
* Parsing
@@ -209,6 +229,14 @@ struct JsonPathParseItem {
uint32 flags;
} like_regex;
+ struct {
+ List *elems;
+ } sequence;
+
+ struct {
+ List *fields;
+ } object;
+
/* scalars */
Numeric numeric;
bool boolean;
diff --git a/src/test/regress/expected/json_jsonpath.out b/src/test/regress/expected/json_jsonpath.out
index 942bf6b..87da5ed 100644
--- a/src/test/regress/expected/json_jsonpath.out
+++ b/src/test/regress/expected/json_jsonpath.out
@@ -123,7 +123,7 @@ select json '[1]' @? 'strict $[1.2]';
select json '[1]' @* 'strict $[1.2]';
ERROR: Invalid SQL/JSON subscript
select json '{}' @* 'strict $[0.3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[0.3]';
?column?
----------
@@ -131,7 +131,7 @@ select json '{}' @? 'lax $[0.3]';
(1 row)
select json '{}' @* 'strict $[1.2]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[1.2]';
?column?
----------
@@ -139,7 +139,7 @@ select json '{}' @? 'lax $[1.2]';
(1 row)
select json '{}' @* 'strict $[-2 to 3]';
-ERROR: SQL/JSON array not found
+ERROR: Invalid SQL/JSON subscript
select json '{}' @? 'lax $[-2 to 3]';
?column?
----------
@@ -1228,6 +1228,25 @@ select json '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select json '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select json '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select json '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1688,6 +1707,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1706,6 +1731,12 @@ SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
?column?
----------
@@ -1730,3 +1761,194 @@ SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select json '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select json '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select json '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select json '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select json 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select json 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index f93c930..feb094c 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -120,6 +120,32 @@ select jsonb '[1]' @? 'strict $[1.2]';
(1 row)
+select jsonb '[1]' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @* 'strict $[0.3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @* 'strict $[1.2]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{}' @* 'strict $[-2 to 3]';
+ERROR: Invalid SQL/JSON subscript
+select jsonb '{}' @? 'lax $[-2 to 3]';
+ ?column?
+----------
+ t
+(1 row)
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
?column?
----------
@@ -254,6 +280,12 @@ select jsonb '1' @* 'lax $[*]';
1
(1 row)
+select jsonb '{}' @* 'lax $[0]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1]' @* 'lax $[0]';
?column?
----------
@@ -287,6 +319,12 @@ select jsonb '[1]' @* '$[last]';
1
(1 row)
+select jsonb '{}' @* 'lax $[last]';
+ ?column?
+----------
+ {}
+(1 row)
+
select jsonb '[1,2,3]' @* '$[last]';
?column?
----------
@@ -1179,8 +1217,6 @@ select jsonb 'null' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb 'true' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
-select jsonb '1' @* '$.datetime()';
-ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '[]' @* '$.datetime()';
?column?
----------
@@ -1192,6 +1228,25 @@ select jsonb '{}' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
select jsonb '""' @* '$.datetime()';
ERROR: Invalid argument for SQL/JSON datetime function
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+ ?column?
+-----------------------------
+ "1970-01-01T00:00:00+00:00"
+(1 row)
+
+select jsonb '0' @* '$.datetime().type()';
+ ?column?
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb '1490216035.5' @* '$.datetime()';
+ ?column?
+-------------------------------
+ "2017-03-22T20:53:55.5+00:00"
+(1 row)
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
?column?
--------------
@@ -1667,6 +1722,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
----------
(0 rows)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
?column?
----------
@@ -1685,6 +1746,12 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
(1 row)
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
+ ?column?
+----------
+ [1, 2]
+(1 row)
+
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
?column?
----------
@@ -1709,3 +1776,194 @@ SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
f
(1 row)
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+ ?column?
+----------
+ 10
+ 20
+ 1
+ 2
+ 3
+ 4
+ 5
+ 30
+(8 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+ ?column?
+----------
+ 10
+ 20
+ 30
+(3 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+ERROR: SQL/JSON member not found
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+ ?column?
+----------
+ -10
+ -20
+ -2
+ -3
+ -4
+ -30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+ ?column?
+----------
+ 10
+ 20.5
+ 2
+ 3
+ 4
+ 30
+(6 rows)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+ ?column?
+----------
+ 4
+(1 row)
+
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+ERROR: Invalid SQL/JSON subscript
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+ ?column?
+----------
+ []
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+ ?column?
+----------
+ 1
+ 2
+ 1
+ 2
+ 3
+ 4
+ 5
+(7 rows)
+
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+ ?column?
+-----------------------
+ [1, 2, 1, 2, 3, 4, 5]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+ ?column?
+-------------------------------
+ [[1, 2], [1, 2, 3, 4], 5, []]
+(1 row)
+
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+ERROR: SQL/JSON member not found
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+ ?column?
+--------------
+ [4, 5, 6, 7]
+(1 row)
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+ ?column?
+----------
+ {}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+ ?column?
+-----------------
+ 5
+ [1, 2, 3, 4, 5]
+(2 rows)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+ ?column?
+--------------------------------
+ {"a": 5, "b": [1, 2, 3, 4, 5]}
+(1 row)
+
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+ ?column?
+---------------------------------------------------------
+ {"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
+(1 row)
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b"]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1}' @* '$["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+ERROR: SQL/JSON member not found
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+ ?column?
+----------
+(0 rows)
+
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+ ?column?
+------------------
+ 2
+ 2
+ 1
+ {"a": 1, "b": 2}
+(4 rows)
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+ ?column?
+----------
+ 1
+(1 row)
+
+select jsonb 'null' @* '{"a": 1}["b"]';
+ ?column?
+----------
+(0 rows)
+
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 193fc68..ea29105 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -510,6 +510,72 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
(1 row)
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+ jsonpath
+------------------------
+ 1, 2 + 3, $."a"[*] + 5
+(1 row)
+
+select '(1, 2, $.a)'::jsonpath;
+ jsonpath
+-------------
+ 1, 2, $."a"
+(1 row)
+
+select '(1, 2, $.a).a[*]'::jsonpath;
+ jsonpath
+----------------------
+ (1, 2, $."a")."a"[*]
+(1 row)
+
+select '(1, 2, $.a) == 5'::jsonpath;
+ jsonpath
+----------------------
+ ((1, 2, $."a") == 5)
+(1 row)
+
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+ jsonpath
+----------------------------
+ $[(1, 2, $."a") to (3, 4)]
+(1 row)
+
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+ jsonpath
+-----------------------------
+ $[(1, (2, $."a")),3,(4, 5)]
+(1 row)
+
+select '[]'::jsonpath;
+ jsonpath
+----------
+ []
+(1 row)
+
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+ jsonpath
+------------------------------------------
+ [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
+(1 row)
+
+select '{}'::jsonpath;
+ jsonpath
+----------
+ {}
+(1 row)
+
+select '{a: 1 + 2}'::jsonpath;
+ jsonpath
+--------------
+ {"a": 1 + 2}
+(1 row)
+
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+ jsonpath
+-----------------------------------------------------------------------
+ {"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
+(1 row)
+
select '$ ? (@.a < 1)'::jsonpath;
jsonpath
---------------
diff --git a/src/test/regress/sql/json_jsonpath.sql b/src/test/regress/sql/json_jsonpath.sql
index 824f510..0901876 100644
--- a/src/test/regress/sql/json_jsonpath.sql
+++ b/src/test/regress/sql/json_jsonpath.sql
@@ -255,6 +255,11 @@ select json '[]' @* 'strict $.datetime()';
select json '{}' @* '$.datetime()';
select json '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select json '0' @* '$.datetime()';
+select json '0' @* '$.datetime().type()';
+select json '1490216035.5' @* '$.datetime()';
+
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select json '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select json '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -367,13 +372,55 @@ set time zone default;
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT json '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT json '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 1)';
SELECT json '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 2)';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT json '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select json '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select json '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select json '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select json '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select json '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select json '[1, 2, 3]' @* '[]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select json '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select json '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select json '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select json '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select json '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select json '[1, 2, 3]' @* '{}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select json '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select json '{"a": 1}' @? '$["a"]';
+select json '{"a": 1}' @? '$["b"]';
+select json '{"a": 1}' @? 'strict $["b"]';
+select json '{"a": 1}' @? '$["b", "a"]';
+
+select json '{"a": 1}' @* '$["a"]';
+select json '{"a": 1}' @* 'strict $["b"]';
+select json '{"a": 1}' @* 'lax $["b"]';
+select json '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select json 'null' @* '{"a": 1}["a"]';
+select json 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index 43f34ef..ad7a320 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -19,6 +19,14 @@ select jsonb '[1]' @? '$[0.5]';
select jsonb '[1]' @? '$[0.9]';
select jsonb '[1]' @? '$[1.2]';
select jsonb '[1]' @? 'strict $[1.2]';
+select jsonb '[1]' @* 'strict $[1.2]';
+select jsonb '{}' @* 'strict $[0.3]';
+select jsonb '{}' @? 'lax $[0.3]';
+select jsonb '{}' @* 'strict $[1.2]';
+select jsonb '{}' @? 'lax $[1.2]';
+select jsonb '{}' @* 'strict $[-2 to 3]';
+select jsonb '{}' @? 'lax $[-2 to 3]';
+
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
@@ -42,12 +50,14 @@ select jsonb '[12, {"a": 13}, {"b": 14}]' @* 'lax $[0 to 10].a';
select jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]' @* '$[2.5 - 1 to $.size() - 2]';
select jsonb '1' @* 'lax $[0]';
select jsonb '1' @* 'lax $[*]';
+select jsonb '{}' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[0]';
select jsonb '[1]' @* 'lax $[*]';
select jsonb '[1,2,3]' @* 'lax $[*]';
select jsonb '[]' @* '$[last]';
select jsonb '[]' @* 'strict $[last]';
select jsonb '[1]' @* '$[last]';
+select jsonb '{}' @* 'lax $[last]';
select jsonb '[1,2,3]' @* '$[last]';
select jsonb '[1,2,3]' @* '$[last - 1]';
select jsonb '[1,2,3]' @* '$[last ? (@.type() == "number")]';
@@ -240,12 +250,16 @@ select jsonb '[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]' @* 'lax $[*] ?
select jsonb 'null' @* '$.datetime()';
select jsonb 'true' @* '$.datetime()';
-select jsonb '1' @* '$.datetime()';
select jsonb '[]' @* '$.datetime()';
select jsonb '[]' @* 'strict $.datetime()';
select jsonb '{}' @* '$.datetime()';
select jsonb '""' @* '$.datetime()';
+-- Standard extension: UNIX epoch to timestamptz
+select jsonb '0' @* '$.datetime()';
+select jsonb '0' @* '$.datetime().type()';
+select jsonb '1490216035.5' @* '$.datetime()';
+
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy")';
select jsonb '"10-03-2017"' @* '$.datetime("dd-mm-yyyy").type()';
select jsonb '"10-03-2017 12:34"' @* '$.datetime("dd-mm-yyyy")';
@@ -373,13 +387,55 @@ set time zone default;
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '$[*] ? (@.a > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @* '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ == 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '$[*].a ? (@ > 10)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @# '[$[*].a]';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 1';
SELECT jsonb '[{"a": 1}, {"a": 2}]' @~ '$[*].a > 2';
+
+-- extension: path sequences
+select jsonb '[1,2,3,4,5]' @* '10, 20, $[*], 30';
+select jsonb '[1,2,3,4,5]' @* 'lax 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* 'strict 10, 20, $[*].a, 30';
+select jsonb '[1,2,3,4,5]' @* '-(10, 20, $[1 to 3], 30)';
+select jsonb '[1,2,3,4,5]' @* 'lax (10, 20.5, $[1 to 3], "30").double()';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 5) ? (@ == 3)]';
+select jsonb '[1,2,3,4,5]' @* '$[(0, $[*], 3) ? (@ == 3)]';
+
+-- extension: array constructors
+select jsonb '[1, 2, 3]' @* '[]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5]';
+select jsonb '[1, 2, 3]' @* '[1, 2, $[*], 4, 5][*]';
+select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
+select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
+select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
+select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
+
+-- extension: object constructors
+select jsonb '[1, 2, 3]' @* '{}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
+select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
+
+-- extension: object subscripting
+select jsonb '{"a": 1}' @? '$["a"]';
+select jsonb '{"a": 1}' @? '$["b"]';
+select jsonb '{"a": 1}' @? 'strict $["b"]';
+select jsonb '{"a": 1}' @? '$["b", "a"]';
+
+select jsonb '{"a": 1}' @* '$["a"]';
+select jsonb '{"a": 1}' @* 'strict $["b"]';
+select jsonb '{"a": 1}' @* 'lax $["b"]';
+select jsonb '{"a": 1, "b": 2}' @* 'lax $["b", "c", "b", "a", 0 to 3]';
+
+select jsonb 'null' @* '{"a": 1}["a"]';
+select jsonb 'null' @* '{"a": 1}["b"]';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 8a3ea42..653f928 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -96,6 +96,20 @@ select '($)'::jsonpath;
select '(($))'::jsonpath;
select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
+select '1, 2 + 3, $.a[*] + 5'::jsonpath;
+select '(1, 2, $.a)'::jsonpath;
+select '(1, 2, $.a).a[*]'::jsonpath;
+select '(1, 2, $.a) == 5'::jsonpath;
+select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
+select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
+
+select '[]'::jsonpath;
+select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
+
+select '{}'::jsonpath;
+select '{a: 1 + 2}'::jsonpath;
+select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
+
select '$ ? (@.a < 1)'::jsonpath;
select '$ ? (@.a < -1)'::jsonpath;
select '$ ? (@.a < +1)'::jsonpath;
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-18 17:58 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-18 17:58 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Nikita, thank you!
I noticed another thing. 3-arguments version of functions
jsonpath_exists(), jsonpath_predicate(), jsonpath_query(),
jsonpath_query_wrapped() are:
1) Not documented
2) Not used in operator definitions
3) Not used in subsequent patches
So, it looks like we can just remove then. But I think we're very
likely can commit jsonpath patch to PostgreSQL 12, but I'm not sure
about other patches. So, having those functions exposed to user can
be extremely useful until we have separate nodes for SQL/JSON. So, I
vote to document these functions.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-18 22:32 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2019-01-18 22:32 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 1/18/19 6:58 PM, Alexander Korotkov wrote:
> Nikita, thank you!
>
> I noticed another thing. 3-arguments version of functions
> jsonpath_exists(), jsonpath_predicate(), jsonpath_query(),
> jsonpath_query_wrapped() are:
>
> 1) Not documented
> 2) Not used in operator definitions
> 3) Not used in subsequent patches
>
> So, it looks like we can just remove then. But I think we're very
> likely can commit jsonpath patch to PostgreSQL 12, but I'm not sure
> about other patches. So, having those functions exposed to user can
> be extremely useful until we have separate nodes for SQL/JSON. So, I
> vote to document these functions.
>
That seems a bit strange. If those functions are meant to be used by
other patches (which ones?) then why should not we make them part of
those future patches?
But it seems more like those functions are actually meant to be used by
users in the first place, in cases when we need to provide a third
parameter (which operators can't do). In that case yes - we should have
them documented properly, but also tested. Which is not the case
currently, because the regression tests only use the operators.
Two more comments:
1) I'm wondering why we even need separate functions for the different
numbers of arguments at the C level, as both simply call to the same
function anyway with a PG_NARGS() condition in it. Can't we ditch those
wrappers and reference that target function directly?
2) I once again ran into the jsonb->json business, which essentially
means that when you do this:
select json '{"a": { "b" : 10}}' @? '$.a.b';
it ends up calling jsonb_jsonpath_exists(), which then does this:
Jsonb *jb = PG_GETARG_JSONB_P(0);
I and am not sure why/how it seems to work, but I find it confusing
because the stack still looks like this:
#0 jsonb_jsonpath_exists (fcinfo=0x162f800) at jsonpath_exec.c:2857
#1 0x000000000096d721 in json_jsonpath_exists2 (fcinfo=0x162f800) at
jsonpath_exec.c:2882
#2 0x00000000006c790a in ExecInterpExpr (state=0x162f300,
econtext=0x162ee18, isnull=0x7ffcea4c3857) at execExprInterp.c:648
...
and of course, the fcinfo->flinfo still points to the json-specific
function, which say the first parameter type is 'json'.
(gdb) print *fcinfo->flinfo
$23 = {fn_addr = 0x96d709 <json_jsonpath_exists2>, fn_oid = 6043,
fn_nargs = 2, fn_strict = true, fn_retset = false, fn_stats = 2 '\002',
fn_extra = 0x0, fn_mcxt = 0x162e990, fn_expr = 0x15db378}
test=# select proname, prosrc, proargtypes from pg_proc where oid = 6043;
proname | prosrc | proargtypes
-----------------+-----------------------+-------------
jsonpath_exists | json_jsonpath_exists2 | 114 6050
(1 row)
test=# select typname from pg_type where oid = 114;
typname
---------
json
(1 row)
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-18 23:54 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-18 23:54 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sat, Jan 19, 2019 at 1:32 AM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> On 1/18/19 6:58 PM, Alexander Korotkov wrote:
> > So, it looks like we can just remove then. But I think we're very
> > likely can commit jsonpath patch to PostgreSQL 12, but I'm not sure
> > about other patches. So, having those functions exposed to user can
> > be extremely useful until we have separate nodes for SQL/JSON. So, I
> > vote to document these functions.
>
> That seems a bit strange. If those functions are meant to be used by
> other patches (which ones?) then why should not we make them part of
> those future patches?
No, these functions aren't used by other patches (it was my original
wrong idea). Other patches provides SQL expressions making these
functions not that necessary. But I think we should keep these
functions in jsonpath patch.
> But it seems more like those functions are actually meant to be used by
> users in the first place, in cases when we need to provide a third
> parameter (which operators can't do). In that case yes - we should have
> them documented properly, but also tested. Which is not the case
> currently, because the regression tests only use the operators.
+1
Thank you for noticing. Tests should be provided as well.
> Two more comments:
>
> 1) I'm wondering why we even need separate functions for the different
> numbers of arguments at the C level, as both simply call to the same
> function anyway with a PG_NARGS() condition in it. Can't we ditch those
> wrappers and reference that target function directly?
That was surprising for me too. Technically, it's OK to do this. And
we do this for extensions. But for in-core functions we have
following sanity check.
-- Considering only built-in procs (prolang = 12), look for multiple uses
-- of the same internal function (ie, matching prosrc fields). It's OK to
-- have several entries with different pronames for the same internal function,
-- but conflicts in the number of arguments and other critical items should
-- be complained of. (We don't check data types here; see next query.)
-- Note: ignore aggregate functions here, since they all point to the same
-- dummy built-in function.
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
WHERE p1.oid < p2.oid AND
p1.prosrc = p2.prosrc AND
p1.prolang = 12 AND p2.prolang = 12 AND
(p1.prokind != 'a' OR p2.prokind != 'a') AND
(p1.prolang != p2.prolang OR
p1.prokind != p2.prokind OR
p1.prosecdef != p2.prosecdef OR
p1.proleakproof != p2.proleakproof OR
p1.proisstrict != p2.proisstrict OR
p1.proretset != p2.proretset OR
p1.provolatile != p2.provolatile OR
p1.pronargs != p2.pronargs);
And we already have some code written especially to make this check happy.
/* This is separate to keep the opr_sanity regression test from complaining */
Datum
regexp_split_to_table_no_flags(PG_FUNCTION_ARGS)
{
return regexp_split_to_table(fcinfo);
}
Personally I'm not fan of this approach, and I would rather relax this
sanity check. But that doesn't seem to be a subject of this patch.
For jsonpath, it's OK to just keep this tradition.
> 2) I once again ran into the jsonb->json business, which essentially
> means that when you do this:
>
> select json '{"a": { "b" : 10}}' @? '$.a.b';
>
> it ends up calling jsonb_jsonpath_exists(), which then does this:
>
> Jsonb *jb = PG_GETARG_JSONB_P(0);
>
> I and am not sure why/how it seems to work, but I find it confusing
> because the stack still looks like this:
>
> #0 jsonb_jsonpath_exists (fcinfo=0x162f800) at jsonpath_exec.c:2857
> #1 0x000000000096d721 in json_jsonpath_exists2 (fcinfo=0x162f800) at
> jsonpath_exec.c:2882
> #2 0x00000000006c790a in ExecInterpExpr (state=0x162f300,
> econtext=0x162ee18, isnull=0x7ffcea4c3857) at execExprInterp.c:648
> ...
>
> and of course, the fcinfo->flinfo still points to the json-specific
> function, which say the first parameter type is 'json'.
>
> (gdb) print *fcinfo->flinfo
> $23 = {fn_addr = 0x96d709 <json_jsonpath_exists2>, fn_oid = 6043,
> fn_nargs = 2, fn_strict = true, fn_retset = false, fn_stats = 2 '\002',
> fn_extra = 0x0, fn_mcxt = 0x162e990, fn_expr = 0x15db378}
>
>
> test=# select proname, prosrc, proargtypes from pg_proc where oid = 6043;
> proname | prosrc | proargtypes
> -----------------+-----------------------+-------------
> jsonpath_exists | json_jsonpath_exists2 | 114 6050
> (1 row)
>
> test=# select typname from pg_type where oid = 114;
> typname
> ---------
> json
> (1 row)
It's tricky. There is jsonpath_json.c, which includes jsonpath_exec.c
with set of macro definitions making that code work with json type.
I'm going to refactor that. But general approach to include same
functions more than once seems OK for me.
Some more notes:
1) It seems that @* and @# are not going to be supported by any
indexes. I think we should remove these operators and let users use
functions instead.
2) I propose to rename @~ operator to @@. We already use @@ as
"satisfies" in multiple places, and I thinks this case fits too.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-19 23:45 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-19 23:45 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sat, Jan 19, 2019 at 2:54 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> 1) It seems that @* and @# are not going to be supported by any
> indexes. I think we should remove these operators and let users use
> functions instead.
> 2) I propose to rename @~ operator to @@. We already use @@ as
> "satisfies" in multiple places, and I thinks this case fits too.
3) How do we calculate the "id" property returned by keyvalue()
function? It's not documented. Even presence of "id" columns isn't
documented. Standard stands that it's implementation-depended
indetifier of object holding key-value pair. The way of its
calculation is also not clear from the code. Why do we need constant
of 10000000000?
id = jb->type != jbvBinary ? 0 :
(int64)((char *) jb->val.binary.data -
(char *) cxt->baseObject.jbc);
id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-20 03:30 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-20 03:30 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sun, Jan 20, 2019 at 2:45 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> 3) How do we calculate the "id" property returned by keyvalue()
> function? It's not documented. Even presence of "id" columns isn't
> documented. Standard stands that it's implementation-depended
> indetifier of object holding key-value pair. The way of its
> calculation is also not clear from the code. Why do we need constant
> of 10000000000?
>
> id = jb->type != jbvBinary ? 0 :
> (int64)((char *) jb->val.binary.data -
> (char *) cxt->baseObject.jbc);
> id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
I've revising patchset bringing it to commitable shape. The
intermediate result is attached.
0001-Preliminary-datetime-infrastructure-v24.patch
* Commit message & minor cleanup
0002-Jsonpath-engine-and-docs-v24.patch
* Support of json is removed. Current implementation is tricky and
cumbersome. We need to design a suitable abstraction layer for that.
It should be done by separate patch applying not only to jsonpath, but
also to other jsonb functions lacking of json analogues.
* jsonpath_exists(jsonb, jsonpath[, jsonb]), jsonpath_predicate(jsonb,
jsonpath[, jsonb]), jsonpath_query(jsonb, jsonpath[, jsonb]),
jsonpath_wrapped(jsonb, jsonpath[, jsonb]) are documented.
* @* and @# operators are removed, @~ operator is renamed to @@.
* Commit message & minor cleanup.
I'll continue revising this patchset. Nikita, could you please write
tests for 3-argument versions of functions? Also, please answer the
question regarding "id" property.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (2 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v24.patch (application/octet-stream, 32.4K) download
commit 7e16e2c93ead29ec6cc39fada1d465ad2fb4b434
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue Jan 15 05:15:34 2019 +0300
Improve datetime conversion infrastructure for upcoming jsonpath
Jsonpath language (part of SQL/JSON standard) includes functions for datetime
conversion. In order to support that, we have to extend our infrastructure
in following ways.
1. FF1-FF6 format patterns implementing different fractions of second. FF3
and FF6 are effectively just synonyms for MS and US. But other fractions
were not implemented yet.
2. to_datetime() internal function, which dynamically determines result
datatype depending on format string.
3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
format patterns.
The first improvement is already user-visible and can use used in datetime
parsing/printing functions. Other improvements are internal, they will be
user-visible together with jsonpath.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Alexander Korotkov, ideas from Oleg Bartunov, Teodor Sigaev
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec17f62..6f5baefc177 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5993,6 +5993,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry><literal>US</literal></entry>
<entry>microsecond (000000-999999)</entry>
</row>
+ <row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
<row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a9785..dfe44533091 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 096d862c1de..fb1635854e7 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,19 +3052,22 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
{
FormatNode *n;
char *s;
int len,
value;
bool fx_mode = false;
+
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
@@ -3149,8 +3197,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3173,6 +3231,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
n->key->name)));
break;
case DCH_TZH:
+
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
@@ -3374,6 +3433,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3470,109 @@ DCH_prevent_counter_overflow(void)
}
}
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3861,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3881,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);
}
@@ -3718,7 +3902,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3739,11 +3923,176 @@ to_date(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result);
}
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4100,15 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*/
static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4161,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4349,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a7e28..5103cd4b84a 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 bec129aff1c..bd15bfa5bb0 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 f5ec9bbd7e0..8a6f2cdb477 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 5b275dc9850..227779cf79b 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabddd9f..44f7cc93bdc 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,22 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c7199934..cdd3c1401ed 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cbb9aa..fea42550913 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c2331..588c3e033fa 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0002-Jsonpath-engine-and-docs-v24.patch (application/octet-stream, 280.8K) download
commit 5f681233e35f96563d651f28c4f2382a8ba90fc8
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue Jan 15 09:25:16 2019 +0300
Implementation of JSON path language
SQL 2016 standards among other things contains set of SQL/JSON features for
JSON processing inside of relational database. The core of SQL/JSON is JSON
path language, allowing access parts of JSON documents and make computations
over them. This commit implements JSON path language as separate datatype
called "jsonpath".
Support of SQL/JSON features requires implementation of separate nodes, and it
will be considered in subsequent patches. This commit includes following
set of plain functions, allowing to execute jsonpath over jsonb values:
* jsonpath_exists(jsonb, jsonpath[, jsonb]),
* jsonpath_predicate(jsonb, jsonpath[, jsonb]),
* jsonpath_query(jsonb, jsonpath[, jsonb]),
* jsonpath_wrapped(jsonb, jsonpath[, jsonb]).
This commit also implements "jsonb @? jsonpath" and "jsonb @@ jsonpath", which
are wrappers over jsonpath_exists(jsonb, jsonpath) and jsonpath_predicate(jsonb,
jsonpath) correspondingly. These operators will have an index support
(implemented in subsequent patches).
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Alexander Korotkov, ideas from Oleg Bartunov, Teodor Sigaev
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 49530241620..f06305d9dca 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6f5baefc177..d45b2466f65 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,661 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Enclosing the path specification into square brackets
+ <literal>[]</literal> automatically wraps the path evaluation
+ result into an array.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>Array of objects containing two members ("key" and "value" of the SQL/JSON item)</entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20"}, {"key": "y", "value": 32}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-extra-op-table">
+ <title>Extended <type>jsonpath</type> Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>min()</literal></entry>
+ <entry>Minimum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.min()</literal></entry>
+ <entry><literal>0</literal></entry>
+ </row>
+ <row>
+ <entry><literal>max()</literal></entry>
+ <entry>Maximum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.max()</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>map()</literal></entry>
+ <entry>Calculate an expression by applying a given function
+ to each element of the json array
+ </entry>
+ <entry><literal>[1, 2, 0]</literal></entry>
+ <entry><literal>$.map(@ * 2)</literal></entry>
+ <entry><literal>[2, 4, 0]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>reduce()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[3, 5, 9]</literal></entry>
+ <entry><literal>$.reduce($1 + $2)</literal></entry>
+ <entry><literal>17</literal></entry>
+ </row>
+ <row>
+ <entry><literal>fold()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[2, 3, 4]</literal></entry>
+ <entry><literal>$.fold($1 * $2, 1)</literal></entry>
+ <entry><literal>24</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldl()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from left to right
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldl([$1, $2], [])</literal></entry>
+ <entry><literal>[[[[], 1], 2], 3]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldr()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from right to left
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldr([$2, $1], [])</literal></entry>
+ <entry><literal>[[[[], 3], 2], 1]</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11977,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11986,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11994,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +12002,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,14 +12010,16 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
@@ -11503,6 +12144,20 @@ table2-mapping
JSON arrays, negative integers count from the end)</entry>
<entry><literal>'["a", {"b":1}]'::jsonb #- '{1,b}'</literal></entry>
</row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>Does JSON path returns any item for the specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@@</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>JSON path predicate check result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -11776,6 +12431,18 @@ table2-mapping
<indexterm>
<primary>jsonb_pretty</primary>
</indexterm>
+ <indexterm>
+ <primary>jsonpath_exists</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonpath_predicate_novars</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonpath_query</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonpath_query_wrapped</primary>
+ </indexterm>
<table id="functions-json-processing-table">
<title>JSON Processing Functions</title>
@@ -12110,6 +12777,131 @@ table2-mapping
</programlisting>
</entry>
</row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonpath_exists(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonpath_exists(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Checks whether JSON path returns any item for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonpath_exists('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonpath_exists('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonpath_predicate(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonpath_predicate(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Returns JSON path predicate result for the specified JSON value.
+ Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonpath_predicate('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] > 2')
+ </literal></para>
+ <para><literal>
+ jsonpath_predicate('{"a":[1,2,3,4,5]}'::jsonb, 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonpath_query(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonpath_query(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>setof jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ select * jsonpath_query('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ > 2)');
+ </literal></para>
+ <para><literal>
+ select * jsonpath_query('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}');
+ </literal></para>
+ </entry>
+ <entry>
+ <para>
+ <programlisting>
+ jsonpath_query
+----------------
+ 3
+ 4
+ 5
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ jsonpath_query
+----------------
+ 2
+ 3
+ 4
+ </programlisting>
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonpath_query_wrapped(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonpath_query_wrapped(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. If there is more than one item, they will be wrapped into an
+ array. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonpath_query_wrapped('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonpath_query_wrapped('{"a":[1,2,3,4,5]}'::jsonb, '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>[3, 4, 5]</literal></para>
+ <para><literal>[2, 3, 4]</literal></para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12939,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +12994,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa0d24..2ba7520b27d 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -535,6 +548,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
therefore ill-suited for applications that often perform such searches.
</para>
+ <para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@~</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @~ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
<para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9bc..31d9d6605d9 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
@@ -308,6 +316,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 99c83c1549c..d2cdd04962d 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 00000000000..7fab054407e
--- /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 20eead17983..1f425c23e8c 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,23 @@ 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_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:
+ rm -f lex.backup
+
+
like.o: like.c like_match.c
varlena.o: varlena.c levenshtein.c
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index de0d0723b71..7526adda431 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without time-zone,
+ * and then use specified time-zone for encoding timestamp
+ * into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c02c8569f28..78f150639e7 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363a4b0..915a8fd8277 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"
@@ -36,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);
@@ -55,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.
@@ -241,6 +239,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");
}
}
@@ -542,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)
{
@@ -580,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.uniquify = true;
break;
case WJB_KEY:
Assert(scalarVal->type == jbvString);
@@ -822,6 +822,7 @@ recurse:
/* Set v to object on first object call */
val->type = jbvObject;
val->val.object.nPairs = (*it)->nElems;
+ val->val.object.uniquify = true;
/*
* v->val.object.pairs is not actually set, because we aren't
@@ -1295,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)
@@ -1741,11 +1742,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
@@ -1758,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;
@@ -1822,6 +1840,9 @@ uniqueifyJsonbObject(JsonbValue *object)
Assert(object->type == jbvObject);
+ if (!object->val.object.uniquify)
+ return;
+
if (object->val.object.nPairs > 1)
qsort_arg(object->val.object.pairs, object->val.object.nPairs, sizeof(JsonbPair),
lengthCompareJsonbPair, &hasNonUniq);
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 00000000000..11d457d505f
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,871 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 00000000000..f404619a9c1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2801 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_exec.c
+ * Routines for SQL/JSON path execution.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#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/formatting.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+#include "utils/varlena.h"
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+
+typedef struct JsonBaseObjectInfo
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ 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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "Wrong type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) 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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+static int
+JsonbArraySize(JsonbValue *jb)
+{
+ if (jb->type == jbvArray)
+ return jb->val.array.nElems;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
+ return JsonContainerSize(jbc);
+ }
+
+ return -1;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Check equality of two SLQ/JSON items of the same type.
+ */
+static inline JsonPathBool
+checkEquality(JsonbValue *jb1, JsonbValue *jb2, bool not)
+{
+ bool eq = false;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ return not ? jpbTrue : jpbFalse;
+
+ return jpbUnknown;
+ }
+
+ 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;
+ 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 jpbUnknown;
+
+ break;
+ }
+
+ case jbvBinary:
+ case jbvObject:
+ case jbvArray:
+ return jpbUnknown;
+
+ default:
+ elog(ERROR, "Unknown jsonb value type %d", jb1->type);
+ }
+
+ return (not ^ eq) ? jpbTrue : jpbFalse;
+}
+
+/*
+ * Compare two SLQ/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+makeCompare(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type != jbvNull && jb2->type != jbvNull)
+ /* non-null items of different types are not order-comparable */
+ return jpbUnknown;
+
+ if (jb1->type != jbvNull || jb2->type != jbvNull)
+ /* comparison of nulls to non-nulls returns always false */
+ return jpbFalse;
+
+ /* both values are JSON nulls */
+ }
+
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+ default:
+ return jpbUnknown;
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ 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)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lseqit = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit = { 0 };
+ JsonbValue *rval;
+
+ while ((rval = JsonValueListNext(&rseq, &rseqit)))
+ {
+ JsonPathBool cmp;
+
+ switch (jsp->type)
+ {
+ case jpiEqual:
+ cmp = checkEquality(lval, rval, false);
+ break;
+ case jpiNotEqual:
+ cmp = checkEquality(lval, rval, true);
+ break;
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ cmp = makeCompare(jsp->type, lval, rval);
+ break;
+ default:
+ elog(ERROR, "Unknown operation");
+ cmp = jpbUnknown;
+ break;
+ }
+
+ if (cmp == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ else if (cmp == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ 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)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ lval = JsonValueListHead(&lseq);
+
+ if (JsonbType(lval) == jbvScalar)
+ lval = JsonbExtractScalar(lval->val.binary.data, &lvalbuf);
+
+ if (lval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ rval = JsonValueListHead(&rseq);
+
+ if (JsonbType(rval) == jbvScalar)
+ rval = JsonbExtractScalar(rval->val.binary.data, &rvalbuf);
+
+ if (rval->type != jbvNumeric)
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if (JsonbType(val) == jbvScalar)
+ JsonbExtractScalar(val->val.binary.data, val);
+
+ if (val->type == jbvNumeric)
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ if (val->type != jbvNumeric)
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1)
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ jbv = JsonValueListHead(&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;
+}
+
+static JsonPathBool
+executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonValueListIterator lit = { 0 };
+ JsonbValue *whole;
+ JsonbValue *initial;
+ JsonbValue initialbuf;
+ bool error = false;
+ bool found = false;
+
+ jspGetRightArg(jsp, &elem);
+ res = recursiveExecute(cxt, &elem, jb, &rseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (JsonValueListLength(&rseq) != 1)
+ return jpbUnknown;
+
+ initial = JsonValueListHead(&rseq);
+
+ if (JsonbType(initial) == jbvScalar)
+ initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
+
+ if (initial->type != jbvString)
+ return jpbUnknown;
+
+ jspGetLeftArg(jsp, &elem);
+ res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((whole = JsonValueListNext(&lseq, &lit)))
+ {
+ JsonbValue wholebuf;
+
+ if (JsonbType(whole) == jbvScalar)
+ whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
+
+ if (whole->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ 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 (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+static JsonPathBool
+executeLikeRegexPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb)
+{
+ JsonPathExecResult res;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *str;
+ 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, &seq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ while ((str = JsonValueListNext(&seq, &it)))
+ {
+ JsonbValue strbuf;
+
+ if (JsonbType(str) == jbvScalar)
+ str = JsonbExtractScalar(str->val.binary.data, &strbuf);
+
+ if (str->type != jbvString)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (RE_compile_and_execute(regex, str->val.string.val,
+ str->val.string.len, cflags,
+ DEFAULT_COLLATION_OID, 0, NULL))
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &arg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+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)
+{
+ 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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ 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);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction
+ * because no function called inside performs data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ 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)
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Interface to jsonpath executor
+ */
+static JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+/*
+ * jsonb_jsonpath_exists
+ */
+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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+/*
+ * jsonb_jsonpath_exists_novars
+ * Implements the 2-argument version of jsonb_jsonpath_exists
+ */
+Datum
+jsonb_jsonpath_exists_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_jsonpath_exists(fcinfo);
+}
+
+Datum
+jsonb_jsonpath_predicate(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ 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_predicate_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_jsonpath_predicate(fcinfo);
+}
+
+Datum
+jsonb_jsonpath_query(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+Datum
+jsonb_jsonpath_query_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_jsonpath_query(fcinfo);
+}
+
+Datum
+jsonb_jsonpath_query_wrapped(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ int size;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ 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_wrapped_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_jsonpath_query_wrapped(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)))
+ {
+ 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);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 00000000000..3856a06ba28
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,495 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "catalog/pg_collation.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 00000000000..d0ab6aa73a1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,629 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scan.l
+ * Lexical parser for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a9290ae..da13a875eb0 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6e5c9..72876533acf 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec0780b9..c6b822497cf 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,13 @@
{ oid => '3287', descr => 'delete path',
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
+{ 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 3ecc2e12c3f..25ee26c5432 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,42 @@
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 => 'implementation of @? operator',
+ proname => 'jsonpath_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_exists_novars' },
+{ oid => '6055', descr => 'jsonpath query without variables',
+ proname => 'jsonpath_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath',
+ prosrc => 'jsonb_jsonpath_query_novars' },
+{ oid => '6124', descr => 'jsonpath query with conditional wrapper without variables',
+ proname => 'jsonpath_query_wrapped', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_query_wrapped_novars' },
+{ oid => '6073', descr => 'implementation of @@ operator',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_jsonpath_predicate_novars' },
+{ oid => '6056', descr => 'jsonpath exists test',
+ proname => 'jsonpath_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_jsonpath_exists' },
+{ oid => '6057', descr => 'jsonpath query',
+ proname => 'jsonpath_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_jsonpath_query' },
+{ oid => '6125', descr => 'jsonpath query with conditional wrapper',
+ proname => 'jsonpath_query_wrapped', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_jsonpath_query_wrapped' },
+{ oid => '6074', descr => 'jsonpath predicate test',
+ proname => 'jsonpath_predicate', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_jsonpath_predicate' },
+
# 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 4b7750d4398..e44c562218d 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 729b8bcfdd9..96b8fe5a1d1 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc090409..4b1e80ddd93 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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a8d6c..e0705e1aa70 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9aad4..bd1668c95a5 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 6ccacf545ce..06ae0b06d8b 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;
@@ -219,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
@@ -236,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,
};
/*
@@ -270,6 +279,8 @@ struct JsonbValue
{
int nPairs; /* 1 pair, 2 elements */
JsonbPair *pairs;
+ bool uniquify; /* Should we sort pairs by key name and
+ * remove duplicate keys? */
} object; /* Associative container type */
struct
@@ -277,11 +288,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -355,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,
@@ -363,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);
@@ -379,5 +403,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 00000000000..5f55eaa0e37
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,285 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 00000000000..923014ffc5d
--- /dev/null
+++ b/src/include/utils/jsonpath_scanner.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 int jsonpath_yyparse(JsonPathParseResult **result);
+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
new file mode 100644
index 00000000000..f771ed7e1ad
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1711 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonpath_query(jsonb '[1]', 'strict $[1]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonpath_query
+----------------
+ 12
+(1 row)
+
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonpath_query
+----------------
+ {"a": 13}
+(1 row)
+
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonpath_query
+----------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonpath_query
+----------------
+ 13
+(1 row)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonpath_query
+----------------
+ 13
+(1 row)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonpath_query
+----------------
+ 13
+ 14
+(2 rows)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonpath_query
+----------------
+ 13
+(1 row)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonpath_query
+----------------
+ 13
+(1 row)
+
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonpath_query
+----------------
+ 13
+(1 row)
+
+select 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 jsonpath_query(jsonb '1', 'lax $[0]');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '1', 'lax $[*]');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '[1]', 'lax $[0]');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '[1]', 'lax $[*]');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
+ jsonpath_query
+----------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonpath_query(jsonb '[]', '$[last]');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '[]', 'strict $[last]');
+ERROR: Invalid SQL/JSON subscript
+select jsonpath_query(jsonb '[1]', '$[last]');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '[1,2,3]', '$[last]');
+ jsonpath_query
+----------------
+ 3
+(1 row)
+
+select jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
+ jsonpath_query
+----------------
+ 2
+(1 row)
+
+select jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonpath_query
+----------------
+ 3
+(1 row)
+
+select jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: Invalid SQL/JSON subscript
+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)
+
+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 jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**');
+ jsonpath_query
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonpath_query
+-----------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonpath_query
+-----------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonpath_query
+----------------
+ {"b": 1}
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonpath_query
+----------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+(0 rows)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonpath_query
+----------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ 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)
+
+--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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+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
+------------------
+ {"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 jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonpath_query(jsonb '[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonpath_query
+----------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonpath_query(jsonb '[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonpath_query
+----------------
+ 0
+(1 row)
+
+select jsonpath_query(jsonb '0', '1 / $');
+ERROR: division by zero
+-- 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
+-- 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 jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+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
+--------------------
+ 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
+----------------
+ "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 '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 '{"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, 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()');
+ 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
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonpath_query(jsonb '[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonpath_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "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
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "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
+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)
+
+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)
+
+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")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonpath_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonpath_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonpath_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonpath_query(jsonb '"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonpath_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonpath_query
+-----------------------------
+ "2017-03-10T12:34:00-05: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-10T12:34:00+05:20"
+(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-10T12:34:00-05:20"
+(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")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ 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")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ 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-10T12:34:00+05:00"
+(1 row)
+
+select jsonpath_query(jsonb '"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonpath_query
+-----------------------------
+ "2017-03-10T12:34:00-05: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-10T12:34:00+05:20"
+(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-10T12:34:00-05:20"
+(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")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonpath_query(jsonb '"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ 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-10T12:34:56+03: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-10T12:34:56+03:10"
+(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-10T03:00:00+03: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-10T03:00:00+03: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-10T01:02:03+04: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-10T13:35:00+01: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-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01: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-10T12:35:00+01: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-10T12:35:00+01: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-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select 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-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonpath_query(jsonb '[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonpath_query
+----------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonpath_query(jsonb '[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonpath_query
+----------------
+(0 rows)
+
+SELECT jsonpath_query_wrapped(jsonb '[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonpath_query_wrapped
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonpath_query_wrapped(jsonb '[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonpath_query_wrapped
+------------------------
+ 1
+(1 row)
+
+SELECT jsonpath_query_wrapped(jsonb '[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonpath_query_wrapped
+------------------------
+
+(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)
+
+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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 00000000000..193fc6841a9
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 44f7cc93bdc..cb3dd190d7e 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1612,7 +1612,6 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff
| 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
(4 rows)
-
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index cc0bbf5db9f..42e0c235956 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 0c10c7100c6..46433c85838 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 00000000000..5635c4cc42b
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,385 @@
+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 jsonpath_query(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 jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonpath_query(jsonb '{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonpath_query(jsonb '[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonpath_query(jsonb '1', 'lax $[0]');
+select jsonpath_query(jsonb '1', 'lax $[*]');
+select jsonpath_query(jsonb '[1]', 'lax $[0]');
+select jsonpath_query(jsonb '[1]', 'lax $[*]');
+select jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
+select jsonpath_query(jsonb '[]', '$[last]');
+select jsonpath_query(jsonb '[]', 'strict $[last]');
+select jsonpath_query(jsonb '[1]', '$[last]');
+select jsonpath_query(jsonb '[1,2,3]', '$[last]');
+select jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
+select jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
+select 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)');
+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 jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonpath_query(jsonb '{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonpath_query(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 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 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 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);
+
+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 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 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)';
+
+-- arithmetic errors
+select jsonpath_query(jsonb '[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonpath_query(jsonb '[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonpath_query(jsonb '0', '1 / $');
+
+-- 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');
+
+-- extension: boolean expressions
+select jsonpath_query(jsonb '2', '$ > 1');
+select jsonpath_query(jsonb '2', '$ <= 1');
+select jsonpath_query(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 '{"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()');
+
+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"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonpath_query(jsonb '"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+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"', '$.datetime("HH24:MI TZH", "+00")');
+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"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+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"', '$.datetime("HH24:MI TZH", "+10")');
+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;
+
+-- jsonpath operators
+
+SELECT jsonpath_query(jsonb '[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonpath_query(jsonb '[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonpath_query_wrapped(jsonb '[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonpath_query_wrapped(jsonb '[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonpath_query_wrapped(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';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 00000000000..8a3ea423b82
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1b20c..0738c37c705 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 e4bb9d2394d..c46bae7fb66 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-20 22:40 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-20 22:40 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sun, Jan 20, 2019 at 6:30 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> I'll continue revising this patchset. Nikita, could you please write
> tests for 3-argument versions of functions? Also, please answer the
> question regarding "id" property.
I've some more notes regarding function set provided in jsonpath patch:
1) Function names don't follow the convention we have. All our
functions dealing with jsonb have "jsonb_" prefix. Exclusions have
"jsonb" in other part of name, for example, "to_jsonb(anyelement)".
We could name functions at SQL level in the same way they are named in
C. So, they would be jsonb_jsonpath_exists() etc. But it's probably
too long. What about jsonb_path_exists() etc?
2) jsonpath_query_wrapped() name seems counter intuitive for me. What
about jsonpath_query_array()?
3) jsonpath_query_wrapped() behavior looks tricky to me. It returns
NULL for no results. When result item is one, it is returned "as is".
When there are two or more result items, they are wrapped into array.
This representation of result seems extremely inconvenient for me. It
becomes hard to solve even trivial tasks with that: for instance,
iterate all items found. Without extra assumptions on what is going
to be returned it's also impossible for caller to distinguish single
array item found from multiple items found wrapped into array. And
that seems very bad. I know this behavior is inspired by SQL/JSON
standard. But since these functions are anyway our extension, we can
implement them as we like. So, I propose to make this function always
return array of items regardless on count of those items (even for 1
or 0 items).
4) If we change behavior of jsonpath_query_wrapped() as above, we can
also implement jsonpath_query_one() function, which would return first
result item or NULL for no items.
Any thoughts?
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-21 13:56 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 0 replies; 198+ messages in thread
From: Nikita Glukhov @ 2019-01-21 13:56 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 20.01.2019 2:45, Alexander Korotkov wrote:
> 3) How do we calculate the "id" property returned by keyvalue()
> function? It's not documented. Even presence of "id" columns isn't
> documented. Standard stands that it's implementation-depended
> indetifier of object holding key-value pair. The way of its
> calculation is also not clear from the code. Why do we need constant
> of 10000000000?
>
> id = jb->type != jbvBinary ? 0 :
> (int64)((char *) jb->val.binary.data -
> (char *) cxt->baseObject.jbc);
> id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
I decided to construct object id from the two parts: base object id and its
binary offset in its base object's jsonb:
object_id = 10000000000 * base_object_id + object_offset_in_base_object
10000000000 (10^10) -- is a first round decimal number greater than 2^32
(maximal offset in jsonb). Decimal multiplier is used here to improve the
readability of identifiers.
Base object is usually a root object of the path: context item '$' or path
variable '$var', literals can't produce objects for now. But if the path
contains generated objects (.keyvalue() itself, for example), then they become
base object for the subsequent .keyvalue(). See example:
'$.a.b.keyvalue().value.keyvalue()' :
- base for the first .keyvalue() is '$'
- base for the second .keyvalue() is '$.a.b.keyvalue()'
Id of '$' is 0.
Id of '$var' is its ordinal (positive) number in the list of variables.
Ids for generated objects are assigned using global counter
'JsonPathExecContext.generatedObjectId' starting from 'number_of_vars + 1'.
Corresponding comments will be added in the upcoming version of the patches.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-21 15:04 Oleg Bartunov <obartunov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Oleg Bartunov @ 2019-01-21 15:04 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Mon, Jan 21, 2019 at 1:40 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
>
> On Sun, Jan 20, 2019 at 6:30 AM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> > I'll continue revising this patchset. Nikita, could you please write
> > tests for 3-argument versions of functions? Also, please answer the
> > question regarding "id" property.
>
> I've some more notes regarding function set provided in jsonpath patch:
> 1) Function names don't follow the convention we have. All our
> functions dealing with jsonb have "jsonb_" prefix. Exclusions have
> "jsonb" in other part of name, for example, "to_jsonb(anyelement)".
> We could name functions at SQL level in the same way they are named in
> C. So, they would be jsonb_jsonpath_exists() etc. But it's probably
> too long. What about jsonb_path_exists() etc?
jsonpath_exists is similar to xpath_exists.
Actually, we could use jsonb_exists(jsonb, jsonpath, json), but then
we should specify the type of the second argument.
> 2) jsonpath_query_wrapped() name seems counter intuitive for me. What
> about jsonpath_query_array()?
The question is should we try to provide a functional interface for
all options of
JSON_QUERY clause ? The same is for other SQL/JSON clauses.
Currently, patch contains very limited subset of JSON_QUERY
functionality, mostly for jsonpath testing.
> 3) jsonpath_query_wrapped() behavior looks tricky to me. It returns
> NULL for no results. When result item is one, it is returned "as is".
> When there are two or more result items, they are wrapped into array.
> This representation of result seems extremely inconvenient for me. It
> becomes hard to solve even trivial tasks with that: for instance,
> iterate all items found. Without extra assumptions on what is going
> to be returned it's also impossible for caller to distinguish single
> array item found from multiple items found wrapped into array. And
> that seems very bad. I know this behavior is inspired by SQL/JSON
> standard. But since these functions are anyway our extension, we can
> implement them as we like. So, I propose to make this function always
> return array of items regardless on count of those items (even for 1
> or 0 items).
Fair enough, but if we agree, that we provide an exact functionality of
SQL clauses, then better to follow the standard to avoid problems.
> 4) If we change behavior of jsonpath_query_wrapped() as above, we can
> also implement jsonpath_query_one() function, which would return first
> result item or NULL for no items.
> Any thoughts?
I think, that we should postpone this functional interface, which could be
added later.
>
> ------
> Alexander Korotkov
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
--
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-21 17:39 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Oleg Bartunov <obartunov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-21 17:39 UTC (permalink / raw)
To: Oleg Bartunov <obartunov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Mon, Jan 21, 2019 at 6:05 PM Oleg Bartunov <obartunov@postgrespro.ru> wrote:
> On Mon, Jan 21, 2019 at 1:40 AM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> >
> > On Sun, Jan 20, 2019 at 6:30 AM Alexander Korotkov
> > <a.korotkov@postgrespro.ru> wrote:
> > > I'll continue revising this patchset. Nikita, could you please write
> > > tests for 3-argument versions of functions? Also, please answer the
> > > question regarding "id" property.
> >
> > I've some more notes regarding function set provided in jsonpath patch:
> > 1) Function names don't follow the convention we have. All our
> > functions dealing with jsonb have "jsonb_" prefix. Exclusions have
> > "jsonb" in other part of name, for example, "to_jsonb(anyelement)".
> > We could name functions at SQL level in the same way they are named in
> > C. So, they would be jsonb_jsonpath_exists() etc. But it's probably
> > too long. What about jsonb_path_exists() etc?
>
> jsonpath_exists is similar to xpath_exists.
That's true. The question is whether it's more important to follow
json[b] naming convention or xml/xpath naming convention? I guess
json[b] naming convention is more important in our case.
> Actually, we could use jsonb_exists(jsonb, jsonpath, json), but then
> we should specify the type of the second argument.
Yes, but AFAICS the key point of json[b]_ prefixes is to evade
function overloading. So, I'm -1 for use jsonb_ prefix and have
function overload because of that.
> > 2) jsonpath_query_wrapped() name seems counter intuitive for me. What
> > about jsonpath_query_array()?
>
> The question is should we try to provide a functional interface for
> all options of
> JSON_QUERY clause ? The same is for other SQL/JSON clauses.
> Currently, patch contains very limited subset of JSON_QUERY
> functionality, mostly for jsonpath testing.
Actually, my point is following. We have jsonpath patch close to the
committable shape. And we have patch for SQL/JSON clauses including
JSON_QUERY, which is huge, complex and didn't receive any serious
review yet. So, we'll did our best on that patch during this release
cycle, but I can't guarantee it will get in PostgreSQL 12. Thus, my
idea is to make jsonpath patch self contained by providing brief and
convenient procedural interface. This procedural interface is anyway
not a part of standard. It *might* be inspired by standard clauses,
but might be not. I think we should try to make this procedural
interface as good and convenient by itself. It's our extension, and
it wouldn't make us more or less standard conforming.
> > 3) jsonpath_query_wrapped() behavior looks tricky to me. It returns
> > NULL for no results. When result item is one, it is returned "as is".
> > When there are two or more result items, they are wrapped into array.
> > This representation of result seems extremely inconvenient for me. It
> > becomes hard to solve even trivial tasks with that: for instance,
> > iterate all items found. Without extra assumptions on what is going
> > to be returned it's also impossible for caller to distinguish single
> > array item found from multiple items found wrapped into array. And
> > that seems very bad. I know this behavior is inspired by SQL/JSON
> > standard. But since these functions are anyway our extension, we can
> > implement them as we like. So, I propose to make this function always
> > return array of items regardless on count of those items (even for 1
> > or 0 items).
>
> Fair enough, but if we agree, that we provide an exact functionality of
> SQL clauses, then better to follow the standard to avoid problems.
No, I see this as our extension. And I don't see problems in being
different from standard clauses, because it's different anyway. For
me, in this case it's better to evade problems of users. And current
behavior of this function seems like just single big pain :)
> > 4) If we change behavior of jsonpath_query_wrapped() as above, we can
> > also implement jsonpath_query_one() function, which would return first
> > result item or NULL for no items.
> > Any thoughts?
>
> I think, that we should postpone this functional interface, which could be
> added later.
The reason we typically postpone things is that they are hard to bring
to committable shape. jsonpath_query_one() doesn't cost us any real
development. So, I don't see point in postponing that if consider
that as good part of procedural jsonpath interface.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-22 05:21 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-22 05:21 UTC (permalink / raw)
To: Oleg Bartunov <obartunov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
The next revision is attached.
Nikita made code and documentation improvements, renamed functions
from "jsonpath_" prefix to "jsonb_path_" prefix. He also renamed
jsonpath_predicate() to jsonb_path_match() (that looks better for me
too).
I've further renamed jsonb_query_wrapped() to jsonb_query_array(), and
changed that behavior to always wrap result into array. Also, I've
introduced new function jsonb_query_first(), which returns first item
from result.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (2 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v25.patch (application/octet-stream, 32.6K) download
commit 9b84b5c2454ba12ac97c952743e56bd287f22ccb
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue Jan 15 05:15:34 2019 +0300
Improve datetime conversion infrastructure for upcoming jsonpath
Jsonpath language (part of SQL/JSON standard) includes functions for datetime
conversion. In order to support that, we have to extend our infrastructure
in following ways.
1. FF1-FF6 format patterns implementing different fractions of second. FF3
and FF6 are effectively just synonyms for MS and US. But other fractions
were not implemented yet.
2. to_datetime() internal function, which dynamically determines result
datatype depending on format string.
3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
format patterns.
The first improvement is already user-visible and can use used in datetime
parsing/printing functions. Other improvements are internal, they will be
user-visible together with jsonpath.
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov and Teodor Sigaev.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec17f62..6f5baefc177 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5993,6 +5993,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry><literal>US</literal></entry>
<entry>microsecond (000000-999999)</entry>
</row>
+ <row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
<row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a9785..dfe44533091 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 096d862c1de..fb1635854e7 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,19 +3052,22 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
{
FormatNode *n;
char *s;
int len,
value;
bool fx_mode = false;
+
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
@@ -3149,8 +3197,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3173,6 +3231,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
n->key->name)));
break;
case DCH_TZH:
+
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
@@ -3374,6 +3433,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3470,109 @@ DCH_prevent_counter_overflow(void)
}
}
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3861,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3881,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);
}
@@ -3718,7 +3902,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3739,11 +3923,176 @@ to_date(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result);
}
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4100,15 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*/
static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4161,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4349,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a7e28..5103cd4b84a 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 bec129aff1c..bd15bfa5bb0 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 f5ec9bbd7e0..8a6f2cdb477 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 5b275dc9850..227779cf79b 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabddd9f..cb3dd190d7e 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c7199934..cdd3c1401ed 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cbb9aa..fea42550913 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c2331..588c3e033fa 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0002-Jsonpath-engine-and-docs-v25.patch (application/octet-stream, 279.5K) download
commit f931c2460b7b3a7c3e2f30dbeafa791e05160d4b
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Tue Jan 15 05:15:34 2019 +0300
Implementation of JSON path language
SQL 2016 standards among other things contains set of SQL/JSON features for
JSON processing inside of relational database. The core of SQL/JSON is JSON
path language, allowing access parts of JSON documents and make computations
over them. This commit implements JSON path language as separate datatype
called "jsonpath".
Support of SQL/JSON features requires implementation of separate nodes, and it
will be considered in subsequent patches. This commit includes following
set of plain functions, allowing to execute jsonpath over jsonb values:
* jsonb_path_exists(jsonb, jsonpath[, jsonb]),
* jsonb_path_match(jsonb, jsonpath[, jsonb]),
* jsonb_path_query(jsonb, jsonpath[, jsonb]),
* jsonb_path_query_array(jsonb, jsonpath[, jsonb]).
* jsonb_path_query_first(jsonb, jsonpath[, jsonb]).
This commit also implements "jsonb @? jsonpath" and "jsonb @@ jsonpath", which
are wrappers over jsonpath_exists(jsonb, jsonpath) and jsonpath_predicate(jsonb,
jsonpath) correspondingly. These operators will have an index support
(implemented in subsequent patches).
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov and Teodor Sigaev.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 49530241620..f06305d9dca 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6f5baefc177..8a9cad71ccb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,661 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Enclosing the path specification into square brackets
+ <literal>[]</literal> automatically wraps the path evaluation
+ result into an array.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>Sequence of object's key-value pairs represented with objects containing three members ("key", "value", and object "id")</entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20", "id": 0}, {"key": "y", "value": 32, "id": 0}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-extra-op-table">
+ <title>Extended <type>jsonpath</type> Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>min()</literal></entry>
+ <entry>Minimum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.min()</literal></entry>
+ <entry><literal>0</literal></entry>
+ </row>
+ <row>
+ <entry><literal>max()</literal></entry>
+ <entry>Maximum value in the json array</entry>
+ <entry><literal>[1, 2, 0, 3, 1]</literal></entry>
+ <entry><literal>$.max()</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>map()</literal></entry>
+ <entry>Calculate an expression by applying a given function
+ to each element of the json array
+ </entry>
+ <entry><literal>[1, 2, 0]</literal></entry>
+ <entry><literal>$.map(@ * 2)</literal></entry>
+ <entry><literal>[2, 4, 0]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>reduce()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[3, 5, 9]</literal></entry>
+ <entry><literal>$.reduce($1 + $2)</literal></entry>
+ <entry><literal>17</literal></entry>
+ </row>
+ <row>
+ <entry><literal>fold()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[2, 3, 4]</literal></entry>
+ <entry><literal>$.fold($1 * $2, 1)</literal></entry>
+ <entry><literal>24</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldl()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from left to right
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldl([$1, $2], [])</literal></entry>
+ <entry><literal>[[[[], 1], 2], 3]</literal></entry>
+ </row>
+ <row>
+ <entry><literal>foldr()</literal></entry>
+ <entry>Calculate an aggregate expression by combining elements
+ of the json array using a given function from right to left
+ with the specified initial value
+ ($1 references the current result, $2 references the current element)
+ </entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$.foldr([$2, $1], [])</literal></entry>
+ <entry><literal>[[[[], 3], 2], 1]</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11977,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11986,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11994,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +12002,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,14 +12010,16 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
@@ -11503,6 +12144,20 @@ table2-mapping
JSON arrays, negative integers count from the end)</entry>
<entry><literal>'["a", {"b":1}]'::jsonb #- '{1,b}'</literal></entry>
</row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>Does JSON path returns any item for the specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@@</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>JSON path predicate check result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @~ '$.a[*] > 2'</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -11776,6 +12431,21 @@ table2-mapping
<indexterm>
<primary>jsonb_pretty</primary>
</indexterm>
+ <indexterm>
+ <primary>jsonb_path_exists</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_match</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_array</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_first</primary>
+ </indexterm>
<table id="functions-json-processing-table">
<title>JSON Processing Functions</title>
@@ -12110,6 +12780,159 @@ table2-mapping
</programlisting>
</entry>
</row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Checks whether JSON path returns any item for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Returns JSON path predicate result for the specified JSON value.
+ Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', '$.a[*] > 2')
+ </literal></para>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>setof jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)');
+ </literal></para>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}');
+ </literal></para>
+ </entry>
+ <entry>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 3
+ 4
+ 5
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 2
+ 3
+ 4
+ </programlisting>
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value and wraps result into an array. Variables are substituted to
+ JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>[3, 4, 5]</literal></para>
+ <para><literal>[2, 3, 4]</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets the first JSON itemsreturned by JSON path for the specified JSON
+ value. Returns <literal>NULL</literal> on no results. Variables are
+ substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>3</literal></para>
+ <para><literal>2</literal></para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12970,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +13025,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa0d24..12a707369f6 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -535,6 +548,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
therefore ill-suited for applications that often perform such searches.
</para>
+ <para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@@</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
<para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9bc..31d9d6605d9 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
@@ -308,6 +316,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/utils/adt/.gitignore b/src/backend/utils/adt/.gitignore
new file mode 100644
index 00000000000..7fab054407e
--- /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 20eead17983..d335eec09a4 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/json.c b/src/backend/utils/adt/json.c
index de0d0723b71..7526adda431 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without time-zone,
+ * and then use specified time-zone for encoding timestamp
+ * into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c02c8569f28..78f150639e7 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363a4b0..c4bb7c8a4e7 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,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 00000000000..78ca1fa403e
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,893 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.c
+ * Input/output and supporting routines for jsonpath
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+static 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;
+ }
+}
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 00000000000..e152042ace1
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2805 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_exec.c
+ * Routines for SQL/JSON path execution.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#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"
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+
+typedef struct JsonBaseObjectInfo
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*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;
+}
+
+static inline JsonbValue *
+extractScalar(JsonbValue *scalar, JsonbValue *buf)
+{
+ if (scalar->type == jbvBinary &&
+ JsonContainerIsScalar(scalar->val.binary.data))
+ scalar = JsonbExtractScalar(scalar->val.binary.data, buf);
+
+ return scalar;
+}
+
+static inline JsonbValue *
+getScalar(JsonbValue *scalar, JsonbValue *buf, int type)
+{
+ scalar = extractScalar(scalar, buf);
+
+ return scalar->type == type ? scalar : NULL;
+}
+
+/* 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)))
+ {
+ JsonbValue bin;
+
+ jbv = extractScalar(jbv, jbv);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &bin);
+
+ pushJsonbValue(&ps, WJB_ELEM, jbv);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
+
+/********************Execute functions for JsonPath***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value.
+ *
+ * If node is a variable then its id returned, otherwise 0 returned.
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "unexpected jsonpath item type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Compare two SQL/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ /*
+ * Equality and order comparison of nulls to non-nulls returns
+ * always false, but inequality comparison returns true.
+ */
+ return op == jpiNotEqual ? jpbTrue : jpbFalse;
+
+ /* Non-null items of different types are not comparable. */
+ return jpbUnknown;
+ }
+
+ switch (jb1->type)
+ {
+ case jbvNull:
+ cmp = 0;
+ break;
+ case jbvBool:
+ cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
+ jb1->val.boolean ? 1 : -1;
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+
+ case jbvBinary:
+ case jbvArray:
+ case jbvObject:
+ return jpbUnknown; /* non-scalars are not comparable */
+
+ default:
+ elog(ERROR, "invalid jsonb value type %d", jb1->type);
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+/* Comparison predicate callback. */
+static inline JsonPathBool
+executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
+{
+ return compareItems(cmp->type, lv, rv);
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ return recursiveExecute(cxt, jsp, jb, found);
+}
+
+typedef JsonPathBool (*JsonPathPredicateCallback)(JsonPathItem *jsp,
+ JsonbValue *larg,
+ JsonbValue *rarg,
+ void *param);
+
+/*
+ * Execute unary or binary predicate.
+ *
+ * Predicates have existence semantics, because their operands are item
+ * sequences. Pairs of items from the left and right operand's sequences are
+ * checked. TRUE returned only if any pair satisfying the condition is found.
+ * In strict mode, even if the desired pair has already been found, all pairs
+ * still need to be examined to check the absence of errors. If any error
+ * occurs, UNKNOWN (analogous to SQL NULL) is returned.
+ */
+static inline JsonPathBool
+executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
+ JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
+ bool unwrapRightArg, JsonPathPredicateCallback exec, void *param)
+{
+ JsonPathExecResult res;
+ JsonValueListIterator lseqit = { 0 };
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ /* Left argument is always auto-unwrapped. */
+ res = recursiveExecuteAndUnwrap(cxt, larg, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (rarg)
+ {
+ /* Right argument is conditionally auto-unwrapped. */
+ res = unwrapRightArg ?
+ recursiveExecuteAndUnwrap(cxt, rarg, jb, &rseq) :
+ recursiveExecute(cxt, rarg, jb, &rseq);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+ }
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit;
+ JsonbValue *rval;
+ int i = 0;
+
+ if (rarg)
+ memset(&rseqit, 0, sizeof(rseqit));
+ else
+ rval = NULL;
+
+ /* Loop only if we have right arg sequence. */
+ while (rarg ? !!(rval = JsonValueListNext(&rseq, &rseqit)) : !i++)
+ {
+ JsonPathBool res = exec(pred, lval, rval, param);
+
+ if (res == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (res == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * Execute binary arithmetic expression on singleton numeric operands.
+ * Array operands are automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1 ||
+ !(lval = getScalar(JsonValueListHead(&lseq), &lvalbuf, jbvNumeric)) ||
+ !(rval = getScalar(JsonValueListHead(&rseq), &rvalbuf, jbvNumeric)))
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if ((val = getScalar(val, val, jbvNumeric)))
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else
+ {
+ if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+ }
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1 ||
+ !(jbv = getScalar(JsonValueListHead(&found), &tmp, jbvNumeric)))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ *index = DatumGetInt32(DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(jbv->val.numeric),
+ Int32GetDatum(0))));
+
+ return jperOk;
+}
+
+/*
+ * STARTS_WITH predicate callback.
+ *
+ * Check if the 'whole' string starts from 'initial' string.
+ */
+static inline JsonPathBool
+executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
+ void *param)
+{
+ JsonbValue wholeBuf;
+ JsonbValue initialBuf;
+
+ if (!(whole = getScalar(whole, &wholeBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (!(initial = getScalar(initial, &initialBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (whole->val.string.len >= initial->val.string.len &&
+ !memcmp(whole->val.string.val,
+ initial->val.string.val,
+ initial->val.string.len))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/* Context for LIKE_REGEX execution. */
+typedef struct LikeRegexContext
+{
+ text *regex;
+ int cflags;
+} LikeRegexContext;
+
+/*
+ * LIKE_REGEX predicate callback.
+ *
+ * Check if the string matches regex pattern.
+ */
+static JsonPathBool
+executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
+ void *param)
+{
+ LikeRegexContext *cxt = param;
+ JsonbValue strbuf;
+
+ if (!(str = getScalar(str, &strbuf, jbvString)))
+ return jpbUnknown;
+
+ /* Cache regex text and converted flags. */
+ if (!cxt->regex)
+ {
+ uint32 flags = jsp->content.like_regex.flags;
+
+ cxt->regex =
+ cstring_to_text_with_len(jsp->content.like_regex.pattern,
+ jsp->content.like_regex.patternlen);
+
+ /* Convert regex flags. */
+ cxt->cflags = REG_ADVANCED;
+
+ if (flags & JSP_REGEX_ICASE)
+ cxt->cflags |= REG_ICASE;
+ if (flags & JSP_REGEX_MLINE)
+ cxt->cflags |= REG_NEWLINE;
+ if (flags & JSP_REGEX_SLINE)
+ cxt->cflags &= ~REG_NEWLINE;
+ if (flags & JSP_REGEX_WSPACE)
+ cxt->cflags |= REG_EXPANDED;
+ }
+
+ if (RE_compile_and_execute(cxt->regex, str->val.string.val,
+ str->val.string.len,
+ cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 larg;
+ JsonPathItem rarg;
+ 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, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbFalse)
+ return jpbFalse;
+
+ /*
+ * SQL/JSON says that we should check second arg
+ * in case of jperError
+ */
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbTrue ? res : res2;
+
+ case jpiOr:
+ jspGetLeftArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbTrue)
+ return jpbTrue;
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbFalse ? res : res2;
+
+ case jpiNot:
+ jspGetArg(jsp, &larg);
+
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbUnknown)
+ return jpbUnknown;
+
+ return res == jpbTrue ? jpbFalse : jpbTrue;
+
+ case jpiIsUnknown:
+ jspGetArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+ return res == jpbUnknown ? jpbTrue : jpbFalse;
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ jspGetLeftArg(jsp, &larg);
+ jspGetRightArg(jsp, &rarg);
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
+ executeComparison, NULL);
+
+ case jpiStartsWith: /* 'whole STARTS WITH initial' */
+ jspGetLeftArg(jsp, &larg); /* 'whole' */
+ jspGetRightArg(jsp, &rarg); /* 'initial' */
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
+ executeStartsWith, NULL);
+
+ case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ {
+ /* 'expr' is a sequence-returning expression.
+ * 'pattern' is a regex string literal. SQL/JSON standard
+ * requires XQuery regexes, but we use Postgres regexes here.
+ * 'flags' is a string literal converted to integer flags at
+ * compile-time.
+ */
+ LikeRegexContext lrcxt = { 0 };
+
+ jspInitByBuffer(&larg, jsp->base, jsp->content.like_regex.expr);
+
+ return executePredicate(cxt, jsp, &larg, NULL, jb, false,
+ executeLikeRegex, &lrcxt);
+ }
+
+ case jpiExists:
+ jspGetArg(jsp, &larg);
+
+ 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, &larg, jb, &vals);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &larg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+/*
+ * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
+ * item onto the stack.
+ */
+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)
+{
+ 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);
+}
+
+/* Save base object and its id needed for the execution of .keyvalue(). */
+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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ break;
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ {
+ JsonbValue jbvbuf;
+
+ if ((jb = getScalar(jb, &jbvbuf, 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ MemoryContext mcxt = CurrentMemoryContext;
+
+ jb = extractScalar(jb, &jbv);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction
+ * because no function called inside performs data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz;
+ bool hasNext;
+
+ res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
+
+ if (!(jb = getScalar(jb, &jbvbuf, jbvString)))
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+
+ /*
+ * .keyvalue() method returns a sequence of object's key-value
+ * pairs in the following format:
+ * '{ "key": key, "value": value, "id": id }'.
+ *
+ * "id" field is an object identifier which is constructed from
+ * the two parts: base object id and its binary offset in base
+ * object's jsonb:
+ * id = 10000000000 * base_object_id + obj_offset_in_base_object
+ *
+ * 10000000000 (10^10) -- is a first round decimal number greater
+ * than 2^32 (maximal offset in jsonb). Decimal multiplier is used
+ * here to improve the readability of identifiers.
+ *
+ * Base object is usually a root object of the path: context item
+ * '$' or path variable '$var', literals can't produce objects for
+ * now. But if the path contains generated objects (.keyvalue()
+ * itself, for example), then they become base object for the
+ * subsequent .keyvalue().
+ *
+ * Id of '$' is 0.
+ * Id of '$var' is its ordinal (positive) number in the list of
+ * variables (see computeJsonPathVariable()).
+ * Ids for generated objects are assigned using global counter
+ * JsonPathExecContext.generatedObjectId starting from
+ * (number_of_vars + 1).
+ */
+
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ idstr.type = jbvString;
+ idstr.val.string.val = "id";
+ idstr.val.string.len = 2;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ /* construct object id from its base object and offset in it */
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Interface to jsonpath executor
+ */
+static JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+/*
+ * jsonb_path_exists
+ * Returns true if jsonpath returns at least one item for the specified
+ * jsonb value.
+ */
+Datum
+jsonb_path_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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+/*
+ * jsonb_path_exists_novars
+ * Implements the 2-argument version of jsonb_path_exists
+ */
+Datum
+jsonb_path_exists_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_exists(fcinfo);
+}
+
+/*
+ * jsonb_path_match
+ * Returns jsonpath predicate result item for the specified jsonb value.
+ * The result of jsonpath execution should be a single item, error is
+ * raised otherwise. Non-bool result is treated as NULL.
+ */
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) != 1)
+ throwJsonPathError(jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED));
+
+ jbv = JsonValueListHead(&found);
+ jbv = extractScalar(jbv, 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);
+}
+
+/*
+ * jsonb_path_match_novars
+ * Implements the 2-argument version of jsonb_path_match
+ */
+Datum
+jsonb_path_match_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_match(fcinfo);
+}
+
+/*
+ * jsonb_path_query
+ * Executes jsonpath for given jsonb document and returns result as
+ * rowset.
+ */
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+/*
+ * jsonb_path_query_novars
+ * Implements the 2-argument version of jsonb_path_query
+ */
+Datum
+jsonb_path_query_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query(fcinfo);
+}
+
+/*
+ * jsonb_path_query_array
+ * Executes jsonpath for given jsonb document and returns result as
+ * jsonb array.
+ */
+Datum
+jsonb_path_query_array(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
+}
+
+/*
+ * jsonb_path_query_array_novars
+ * Implements the 2-argument version of jsonb_path_query_array
+ */
+Datum
+jsonb_path_query_array_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_array(fcinfo);
+}
+
+/*
+ * jsonb_path_query_first
+ * Executes jsonpath for given jsonb document and returns first result
+ * item. If there are no items, NULL returned.
+ */
+Datum
+jsonb_path_query_first(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) >= 1)
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * jsonb_path_query_first_novars
+ * Implements the 2-argument version of jsonb_path_query_first
+ */
+Datum
+jsonb_path_query_first_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_first(fcinfo);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 00000000000..b54f7d67968
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,494 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+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); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 00000000000..89e7f938f36
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scan.l
+ * Lexical parser for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a9290ae..da13a875eb0 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6e5c9..72876533acf 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec0780b9..1c7af92eb19 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,13 @@
{ oid => '3287', descr => 'delete path',
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
+{ oid => '6076', descr => 'jsonpath exists',
+ oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_exists(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6107', descr => 'jsonpath match',
+ oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_match(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ecc2e12c3f..00e254bb084 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,47 @@
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 => 'implementation of @? operator',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_novars' },
+{ oid => '6055', descr => 'jsonpath query without variables',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath',
+ prosrc => 'jsonb_path_query_novars' },
+{ oid => '6124', descr => 'jsonpath query without variables wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_array_novars' },
+{ oid => '6122', descr => 'jsonpath query without variables first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_first_novars' },
+{ oid => '6073', descr => 'implementation of @@ operator',
+ proname => 'jsonb_path_match', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_novars' },
+{ oid => '6056', descr => 'jsonpath exists test',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_exists' },
+{ oid => '6057', descr => 'jsonpath query',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query' },
+{ oid => '6125', descr => 'jsonpath query wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query_array' },
+{ oid => '6123', descr => 'jsonpath query first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_query_first' },
+{ oid => '6074', descr => 'jsonpath match', proname => 'jsonb_path_match',
+ prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_match' },
+
# 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 4b7750d4398..e44c562218d 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc090409..4b1e80ddd93 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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a8d6c..e0705e1aa70 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9aad4..bd1668c95a5 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 6ccacf545ce..80962b7d431 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;
@@ -236,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,
};
/*
@@ -277,11 +286,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +397,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 00000000000..daef0c82bd9
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,283 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.h
+ * Definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+#endif
diff --git a/src/include/utils/jsonpath_gram.h b/src/include/utils/jsonpath_gram.h
new file mode 120000
index 00000000000..b3abe1bc616
--- /dev/null
+++ b/src/include/utils/jsonpath_gram.h
@@ -0,0 +1 @@
+/Users/smagen/projects/postgresql/env/master/src/src/backend/utils/adt/jsonpath_gram.h
\ No newline at end of file
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 00000000000..419dc934e2f
--- /dev/null
+++ b/src/include/utils/jsonpath_scanner.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scanner.h
+ * Definitions for jsonpath scanner & parser
+ *
+ * Portions Copyright (c) 2019, 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 int jsonpath_yyparse(JsonPathParseResult **result);
+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
new file mode 100644
index 00000000000..5277bbe3071
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1765 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'strict $[1]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonb_path_query
+------------------
+ 12
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonb_path_query
+------------------
+ {"a": 13}
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonb_path_query
+------------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonb_path_query
+------------------
+ 13
+ 14
+(2 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ jsonb_path_query
+------------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb_path_query('1', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[]', '$[last]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[last]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb_path_query('[1]', '$[last]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+ jsonb_path_query
+------------------
+ 2
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: Invalid SQL/JSON subscript
+select * from jsonb_path_query('{"a": 10}', '$');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)');
+ERROR: could not find jsonpath variable 'value'
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+(1 row)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ jsonb_path_query
+------------------
+ 1
+ "2"
+(2 rows)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (.a == .b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (.a == .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == (.b + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == -.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (.a == - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (.a == 1 - - .b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (.a == 1 - +.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
+select jsonb_path_query('0', '1 / $');
+ERROR: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ jsonb_path_query
+------------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('2', '$ <= 1');
+ jsonb_path_query
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('2', '$ == "2"');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,2,3]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ jsonb_path_query
+------------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb_path_query('null', 'null.type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('null', 'true.type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('null', '123.type()');
+ jsonb_path_query
+------------------
+ "number"
+(1 row)
+
+select jsonb_path_query('null', '"123".type()');
+ jsonb_path_query
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+ jsonb_path_query
+------------------
+ 4
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: SQL/JSON array not found
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ jsonb_path_query
+------------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ jsonb_path_query
+------------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('{}', '$.keyvalue()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ jsonb_path_query
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('null', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('true', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('[]', '$.double()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('{}', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('1.23', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ jsonb_path_query
+------------------
+ null
+ 1
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+ jsonb_path_query
+------------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('true', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('1', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('""', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_array
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_array
+------------------------
+ [1]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ [2, 3]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_first
+------------------------
+ 2
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_first
+------------------------
+
+(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)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_exists
+-------------------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 00000000000..193fc6841a9
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,800 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5db9f..42e0c235956 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 0c10c7100c6..46433c85838 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 00000000000..9107df1dd3c
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,395 @@
+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_path_query('[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_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonb_path_query('1', 'lax $[0]');
+select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('[1]', 'lax $[0]');
+select jsonb_path_query('[1]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+select jsonb_path_query('[]', '$[last]');
+select jsonb_path_query('[]', 'strict $[last]');
+select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+
+select * from jsonb_path_query('{"a": 10}', '$');
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)');
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)', '{"value" : 13}');
+select * from jsonb_path_query('{"a": 10}', '$ ? (.a < $value)', '{"value" : 8}');
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+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 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_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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 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_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == 1 + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == (1 + 1))');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (.a == .b + 1)');
+select jsonb_path_query('{"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)';
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonb_path_query('0', '1 / $');
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+select jsonb_path_query('2', '$ <= 1');
+select jsonb_path_query('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 jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select jsonb_path_query('null', 'null.type()');
+select jsonb_path_query('null', 'true.type()');
+select jsonb_path_query('null', '123.type()');
+select jsonb_path_query('null', '"123".type()');
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+select jsonb_path_query('{}', '$.keyvalue()');
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+
+select jsonb_path_query('null', '$.double()');
+select jsonb_path_query('true', '$.double()');
+select jsonb_path_query('[]', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()');
+select jsonb_path_query('{}', '$.double()');
+select jsonb_path_query('1.23', '$.double()');
+select jsonb_path_query('"1.23"', '$.double()');
+select jsonb_path_query('"1.23aaa"', '$.double()');
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 00000000000..8a3ea423b82
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,146 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? ((@.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 '$a'::jsonpath;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1b20c..0738c37c705 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 e4bb9d2394d..c46bae7fb66 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-22 09:12 Oleg Bartunov <obartunov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Oleg Bartunov @ 2019-01-22 09:12 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 22, 2019 at 8:21 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
>
> The next revision is attached.
>
> Nikita made code and documentation improvements, renamed functions
> from "jsonpath_" prefix to "jsonb_path_" prefix. He also renamed
> jsonpath_predicate() to jsonb_path_match() (that looks better for me
> too).
>
> I've further renamed jsonb_query_wrapped() to jsonb_query_array(), and
> changed that behavior to always wrap result into array.
agree with new names
so it mimic the behaviour of JSON_QUERY with UNCONDITIONAL WRAPPER option
Also, I've
> introduced new function jsonb_query_first(), which returns first item
> from result.
>
> ------
> Alexander Korotkov
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
--
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-23 00:41 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Oleg Bartunov <obartunov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2019-01-23 00:41 UTC (permalink / raw)
To: Oleg Bartunov <obartunov@postgrespro.ru>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 26th version of the patches:
* Documentation:
- Fixed some mistakes
- Removed mention of syntax extensions not present in this patch
- Documented '.datetime(format, default_tz)'
* Removed accidental syntax extension allowing to write '@.key' without '@'
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (2 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v26.patch (text/x-patch, 33.1K) download
From f9d37b576ed892eb76a15289cab540682c7973f2 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 23 Jan 2019 03:21:45 +0300
Subject: [PATCH 01/14] Improve datetime conversion infrastructure for upcoming
jsonpath
Jsonpath language (part of SQL/JSON standard) includes functions for datetime
conversion. In order to support that, we have to extend our infrastructure
in following ways.
1. FF1-FF6 format patterns implementing different fractions of second. FF3
and FF6 are effectively just synonyms for MS and US. But other fractions
were not implemented yet.
2. to_datetime() internal function, which dynamically determines result
datatype depending on format string.
3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
format patterns.
The first improvement is already user-visible and can use used in datetime
parsing/printing functions. Other improvements are internal, they will be
user-visible together with jsonpath.
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov and Teodor Sigaev.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
---
doc/src/sgml/func.sgml | 24 ++
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 408 ++++++++++++++++++++++++++++--
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 79 ++++++
src/test/regress/expected/timestamp.out | 15 ++
src/test/regress/expected/timestamptz.out | 15 ++
src/test/regress/sql/horology.sql | 9 +
src/test/regress/sql/timestamp.sql | 8 +
src/test/regress/sql/timestamptz.sql | 8 +
13 files changed, 554 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec1..6f5baef 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5994,6 +5994,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry>microsecond (000000-999999)</entry>
</row>
<row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
+ <row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
</row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a..dfe4453 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 096d862..fb16358 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,19 +3052,22 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
{
FormatNode *n;
char *s;
int len,
value;
bool fx_mode = false;
+
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
@@ -3149,8 +3197,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3173,6 +3231,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
n->key->name)));
break;
case DCH_TZH:
+
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
@@ -3374,6 +3433,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3470,109 @@ DCH_prevent_counter_overflow(void)
}
}
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3861,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3881,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);
}
@@ -3718,7 +3902,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3924,175 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
+/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4100,15 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*/
static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4161,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4349,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a..5103cd4 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 bec129a..bd15bfa 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 f5ec9bb..8a6f2cd 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 5b275dc..227779c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..74ecb7c 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..cb3dd19 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..cdd3c14 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..3c85803 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..fea4255 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..588c3e0 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
--
2.7.4
0002-Jsonpath-engine-and-docs-v26.patch (text/x-patch, 277.7K) download
From ec8c27ab7eacac3973d53cd6f7f4391b503c16b0 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 23 Jan 2019 03:21:45 +0300
Subject: [PATCH 02/14] Implementation of JSON path language
SQL 2016 standards among other things contains set of SQL/JSON features for
JSON processing inside of relational database. The core of SQL/JSON is JSON
path language, allowing access parts of JSON documents and make computations
over them. This commit implements JSON path language as separate datatype
called "jsonpath".
Support of SQL/JSON features requires implementation of separate nodes, and it
will be considered in subsequent patches. This commit includes following
set of plain functions, allowing to execute jsonpath over jsonb values:
* jsonb_path_exists(jsonb, jsonpath[, jsonb]),
* jsonb_path_match(jsonb, jsonpath[, jsonb]),
* jsonb_path_query(jsonb, jsonpath[, jsonb]),
* jsonb_path_query_array(jsonb, jsonpath[, jsonb]).
* jsonb_path_query_first(jsonb, jsonpath[, jsonb]).
This commit also implements "jsonb @? jsonpath" and "jsonb @@ jsonpath", which
are wrappers over jsonpath_exists(jsonb, jsonpath) and jsonpath_predicate(jsonb,
jsonpath) correspondingly. These operators will have an index support
(implemented in subsequent patches).
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov and Teodor Sigaev.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
---
doc/src/sgml/biblio.sgml | 11 +
doc/src/sgml/func.sgml | 754 ++++++-
doc/src/sgml/json.sgml | 250 ++-
src/backend/Makefile | 11 +-
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 18 +-
src/backend/utils/adt/json.c | 32 +-
src/backend/utils/adt/jsonb.c | 12 +-
src/backend/utils/adt/jsonb_util.c | 21 +
src/backend/utils/adt/jsonpath.c | 893 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2805 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 493 +++++
src/backend/utils/adt/jsonpath_scan.l | 630 ++++++
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 8 +
src/include/catalog/pg_proc.dat | 41 +
src/include/catalog/pg_type.dat | 5 +
src/include/regex/regex.h | 5 +
src/include/utils/.gitignore | 1 +
src/include/utils/jsonapi.h | 3 +-
src/include/utils/jsonb.h | 25 +-
src/include/utils/jsonpath.h | 283 +++
src/include/utils/jsonpath_scanner.h | 31 +
src/test/regress/expected/jsonb_jsonpath.out | 1765 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 788 ++++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 2 +
src/test/regress/sql/jsonb_jsonpath.sql | 395 ++++
src/test/regress/sql/jsonpath.sql | 144 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
32 files changed, 9446 insertions(+), 30 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/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 4953024..f06305d 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6f5baef..6331d1a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,578 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>, <replaceable>default_tz</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template and default timezone</entry>
+ <entry><literal>["12:30 -02", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI TZH", "+03:00")</literal></entry>
+ <entry><literal>"12:30:00-02:00", "18:40:00+03:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>Sequence of object's key-value pairs represented with objects containing three members ("key", "value", and object "id")</entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20", "id": 0}, {"key": "y", "value": 32, "id": 0}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11894,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11903,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11911,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +11919,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,14 +11927,16 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
@@ -11503,6 +12061,18 @@ table2-mapping
JSON arrays, negative integers count from the end)</entry>
<entry><literal>'["a", {"b":1}]'::jsonb #- '{1,b}'</literal></entry>
</row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>Does JSON path returns any item for the specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)'</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@@</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>JSON path predicate check result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @@ '$.a[*] > 2'</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -11776,6 +12346,21 @@ table2-mapping
<indexterm>
<primary>jsonb_pretty</primary>
</indexterm>
+ <indexterm>
+ <primary>jsonb_path_exists</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_match</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_array</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_first</primary>
+ </indexterm>
<table id="functions-json-processing-table">
<title>JSON Processing Functions</title>
@@ -12110,6 +12695,159 @@ table2-mapping
</programlisting>
</entry>
</row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Checks whether JSON path returns any item for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Returns JSON path predicate result for the specified JSON value.
+ Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', '$.a[*] > 2')
+ </literal></para>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>setof jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)');
+ </literal></para>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}');
+ </literal></para>
+ </entry>
+ <entry>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 3
+ 4
+ 5
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 2
+ 3
+ 4
+ </programlisting>
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value and wraps result into an array. Variables are substituted to
+ JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>[3, 4, 5]</literal></para>
+ <para><literal>[2, 3, 4]</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets the first JSON item returned by JSON path for the specified JSON
+ value. Returns <literal>NULL</literal> on no results. Variables are
+ substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>3</literal></para>
+ <para><literal>2</literal></para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12885,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +12940,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa..12a7073 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -536,6 +549,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@@</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96d..31d9d66 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
@@ -308,6 +316,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/utils/adt/.gitignore b/src/backend/utils/adt/.gitignore
new file mode 100644
index 0000000..7fab054
--- /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 20eead1..d335eec 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/json.c b/src/backend/utils/adt/json.c
index de0d072..7526add 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without time-zone,
+ * and then use specified time-zone for encoding timestamp
+ * into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c02c856..78f1506 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:
@@ -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/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363..c4bb7c8 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,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..78ca1fa
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,893 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.c
+ * Input/output and supporting routines for jsonpath
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+static 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;
+ }
+}
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char)(item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char*)item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char*)&item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *)(buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32*)(buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char*)&item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *)(buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */);
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4); break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4); 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13); 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf,"exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */);
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t)(base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch(v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus
+ );
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(
+ v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith
+ );
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(
+ v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith
+ );
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool)*v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric)v->content.value.data;
+}
+
+char*
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(
+ v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable
+ );
+
+ if (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
new file mode 100644
index 0000000..ecbde61
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2805 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_exec.c
+ * Routines for SQL/JSON path execution.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#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"
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+
+typedef struct JsonBaseObjectInfo
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*out));
+
+ return JsonbInitBinary(out, jb);
+}
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+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;
+}
+
+static inline JsonbValue *
+extractScalar(JsonbValue *scalar, JsonbValue *buf)
+{
+ if (scalar->type == jbvBinary &&
+ JsonContainerIsScalar(scalar->val.binary.data))
+ scalar = JsonbExtractScalar(scalar->val.binary.data, buf);
+
+ return scalar;
+}
+
+static inline JsonbValue *
+getScalar(JsonbValue *scalar, JsonbValue *buf, int type)
+{
+ scalar = extractScalar(scalar, buf);
+
+ return scalar->type == type ? scalar : NULL;
+}
+
+/* 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)))
+ {
+ JsonbValue bin;
+
+ jbv = extractScalar(jbv, jbv);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &bin);
+
+ pushJsonbValue(&ps, WJB_ELEM, jbv);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
+
+/********************Execute functions for JsonPath***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ JsonbExtractScalar(&jb->root, value);
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value.
+ *
+ * If node is a variable then its id returned, otherwise 0 returned.
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "unexpected jsonpath item type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(
+ DirectFunctionCall2(
+ numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Compare two SQL/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+ /*
+ * Equality and order comparison of nulls to non-nulls returns
+ * always false, but inequality comparison returns true.
+ */
+ return op == jpiNotEqual ? jpbTrue : jpbFalse;
+
+ /* Non-null items of different types are not comparable. */
+ return jpbUnknown;
+ }
+
+ switch (jb1->type)
+ {
+ case jbvNull:
+ cmp = 0;
+ break;
+ case jbvBool:
+ cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
+ jb1->val.boolean ? 1 : -1;
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+
+ case jbvBinary:
+ case jbvArray:
+ case jbvObject:
+ return jpbUnknown; /* non-scalars are not comparable */
+
+ default:
+ elog(ERROR, "invalid jsonb value type %d", jb1->type);
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+/* Comparison predicate callback. */
+static inline JsonPathBool
+executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
+{
+ return compareItems(cmp->type, lv, rv);
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ return recursiveExecute(cxt, jsp, jb, found);
+}
+
+typedef JsonPathBool (*JsonPathPredicateCallback)(JsonPathItem *jsp,
+ JsonbValue *larg,
+ JsonbValue *rarg,
+ void *param);
+
+/*
+ * Execute unary or binary predicate.
+ *
+ * Predicates have existence semantics, because their operands are item
+ * sequences. Pairs of items from the left and right operand's sequences are
+ * checked. TRUE returned only if any pair satisfying the condition is found.
+ * In strict mode, even if the desired pair has already been found, all pairs
+ * still need to be examined to check the absence of errors. If any error
+ * occurs, UNKNOWN (analogous to SQL NULL) is returned.
+ */
+static inline JsonPathBool
+executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
+ JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
+ bool unwrapRightArg, JsonPathPredicateCallback exec, void *param)
+{
+ JsonPathExecResult res;
+ JsonValueListIterator lseqit = { 0 };
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ /* Left argument is always auto-unwrapped. */
+ res = recursiveExecuteAndUnwrap(cxt, larg, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (rarg)
+ {
+ /* Right argument is conditionally auto-unwrapped. */
+ res = unwrapRightArg ?
+ recursiveExecuteAndUnwrap(cxt, rarg, jb, &rseq) :
+ recursiveExecute(cxt, rarg, jb, &rseq);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+ }
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit;
+ JsonbValue *rval;
+ int i = 0;
+
+ if (rarg)
+ memset(&rseqit, 0, sizeof(rseqit));
+ else
+ rval = NULL;
+
+ /* Loop only if we have right arg sequence. */
+ while (rarg ? !!(rval = JsonValueListNext(&rseq, &rseqit)) : !i++)
+ {
+ JsonPathBool res = exec(pred, lval, rval, param);
+
+ if (res == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (res == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * Execute binary arithmetic expression on singleton numeric operands.
+ * Array operands are automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = { 0 };
+ JsonValueList rseq = { 0 };
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1 ||
+ !(lval = getScalar(JsonValueListHead(&lseq), &lvalbuf, jbvNumeric)) ||
+ !(rval = getScalar(JsonValueListHead(&rseq), &rvalbuf, jbvNumeric)))
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = { 0 };
+ JsonValueListIterator it = { 0 };
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if ((val = getScalar(val, val, jbvNumeric)))
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else
+ {
+ if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+ }
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1 ||
+ !(jbv = getScalar(JsonValueListHead(&found), &tmp, jbvNumeric)))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ *index = DatumGetInt32(DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(jbv->val.numeric),
+ Int32GetDatum(0))));
+
+ return jperOk;
+}
+
+/*
+ * STARTS_WITH predicate callback.
+ *
+ * Check if the 'whole' string starts from 'initial' string.
+ */
+static inline JsonPathBool
+executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
+ void *param)
+{
+ JsonbValue wholeBuf;
+ JsonbValue initialBuf;
+
+ if (!(whole = getScalar(whole, &wholeBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (!(initial = getScalar(initial, &initialBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (whole->val.string.len >= initial->val.string.len &&
+ !memcmp(whole->val.string.val,
+ initial->val.string.val,
+ initial->val.string.len))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/* Context for LIKE_REGEX execution. */
+typedef struct LikeRegexContext
+{
+ text *regex;
+ int cflags;
+} LikeRegexContext;
+
+/*
+ * LIKE_REGEX predicate callback.
+ *
+ * Check if the string matches regex pattern.
+ */
+static JsonPathBool
+executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
+ void *param)
+{
+ LikeRegexContext *cxt = param;
+ JsonbValue strbuf;
+
+ if (!(str = getScalar(str, &strbuf, jbvString)))
+ return jpbUnknown;
+
+ /* Cache regex text and converted flags. */
+ if (!cxt->regex)
+ {
+ uint32 flags = jsp->content.like_regex.flags;
+
+ cxt->regex =
+ cstring_to_text_with_len(jsp->content.like_regex.pattern,
+ jsp->content.like_regex.patternlen);
+
+ /* Convert regex flags. */
+ cxt->cflags = REG_ADVANCED;
+
+ if (flags & JSP_REGEX_ICASE)
+ cxt->cflags |= REG_ICASE;
+ if (flags & JSP_REGEX_MLINE)
+ cxt->cflags |= REG_NEWLINE;
+ if (flags & JSP_REGEX_SLINE)
+ cxt->cflags &= ~REG_NEWLINE;
+ if (flags & JSP_REGEX_WSPACE)
+ cxt->cflags |= REG_EXPANDED;
+ }
+
+ if (RE_compile_and_execute(cxt->regex, str->val.string.val,
+ str->val.string.len,
+ cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because no
+ * function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 larg;
+ JsonPathItem rarg;
+ 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, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbFalse)
+ return jpbFalse;
+
+ /*
+ * SQL/JSON says that we should check second arg
+ * in case of jperError
+ */
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbTrue ? res : res2;
+
+ case jpiOr:
+ jspGetLeftArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbTrue)
+ return jpbTrue;
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbFalse ? res : res2;
+
+ case jpiNot:
+ jspGetArg(jsp, &larg);
+
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbUnknown)
+ return jpbUnknown;
+
+ return res == jpbTrue ? jpbFalse : jpbTrue;
+
+ case jpiIsUnknown:
+ jspGetArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+ return res == jpbUnknown ? jpbTrue : jpbFalse;
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ jspGetLeftArg(jsp, &larg);
+ jspGetRightArg(jsp, &rarg);
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
+ executeComparison, NULL);
+
+ case jpiStartsWith: /* 'whole STARTS WITH initial' */
+ jspGetLeftArg(jsp, &larg); /* 'whole' */
+ jspGetRightArg(jsp, &rarg); /* 'initial' */
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
+ executeStartsWith, NULL);
+
+ case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ {
+ /* 'expr' is a sequence-returning expression.
+ * 'pattern' is a regex string literal. SQL/JSON standard
+ * requires XQuery regexes, but we use Postgres regexes here.
+ * 'flags' is a string literal converted to integer flags at
+ * compile-time.
+ */
+ LikeRegexContext lrcxt = { 0 };
+
+ jspInitByBuffer(&larg, jsp->base, jsp->content.like_regex.expr);
+
+ return executePredicate(cxt, jsp, &larg, NULL, jb, false,
+ executeLikeRegex, &lrcxt);
+ }
+
+ case jpiExists:
+ jspGetArg(jsp, &larg);
+
+ 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, &larg, jb, &vals);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &larg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+/*
+ * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
+ * item onto the stack.
+ */
+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)
+{
+ 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);
+}
+
+/* Save base object and its id needed for the execution of .keyvalue(). */
+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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ break;
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ {
+ JsonbValue jbvbuf;
+
+ if ((jb = getScalar(jb, &jbvbuf, 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ MemoryContext mcxt = CurrentMemoryContext;
+
+ jb = extractScalar(jb, &jbv);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction
+ * because no function called inside performs data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz;
+ bool hasNext;
+
+ res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
+
+ if (!(jb = getScalar(jb, &jbvbuf, jbvString)))
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = { 0 };
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = { 0 };
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+
+ /*
+ * .keyvalue() method returns a sequence of object's key-value
+ * pairs in the following format:
+ * '{ "key": key, "value": value, "id": id }'.
+ *
+ * "id" field is an object identifier which is constructed from
+ * the two parts: base object id and its binary offset in base
+ * object's jsonb:
+ * id = 10000000000 * base_object_id + obj_offset_in_base_object
+ *
+ * 10000000000 (10^10) -- is a first round decimal number greater
+ * than 2^32 (maximal offset in jsonb). Decimal multiplier is used
+ * here to improve the readability of identifiers.
+ *
+ * Base object is usually a root object of the path: context item
+ * '$' or path variable '$var', literals can't produce objects for
+ * now. But if the path contains generated objects (.keyvalue()
+ * itself, for example), then they become base object for the
+ * subsequent .keyvalue().
+ *
+ * Id of '$' is 0.
+ * Id of '$var' is its ordinal (positive) number in the list of
+ * variables (see computeJsonPathVariable()).
+ * Ids for generated objects are assigned using global counter
+ * JsonPathExecContext.generatedObjectId starting from
+ * (number_of_vars + 1).
+ */
+
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ idstr.type = jbvString;
+ idstr.val.string.val = "id";
+ idstr.val.string.len = 2;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ /* construct object id from its base object and offset in it */
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Interface to jsonpath executor
+ */
+static JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+/*
+ * jsonb_path_exists
+ * Returns true if jsonpath returns at least one item for the specified
+ * jsonb value.
+ */
+Datum
+jsonb_path_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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+/*
+ * jsonb_path_exists_novars
+ * Implements the 2-argument version of jsonb_path_exists
+ */
+Datum
+jsonb_path_exists_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_exists(fcinfo);
+}
+
+/*
+ * jsonb_path_match
+ * Returns jsonpath predicate result item for the specified jsonb value.
+ * The result of jsonpath execution should be a single item, error is
+ * raised otherwise. Non-bool result is treated as NULL.
+ */
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonbValue *jbv;
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) != 1)
+ throwJsonPathError(jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED));
+
+ jbv = JsonValueListHead(&found);
+ jbv = extractScalar(jbv, 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);
+}
+
+/*
+ * jsonb_path_match_novars
+ * Implements the 2-argument version of jsonb_path_match
+ */
+Datum
+jsonb_path_match_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_match(fcinfo);
+}
+
+/*
+ * jsonb_path_query
+ * Executes jsonpath for given jsonb document and returns result as
+ * rowset.
+ */
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = { 0 };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+/*
+ * jsonb_path_query_novars
+ * Implements the 2-argument version of jsonb_path_query
+ */
+Datum
+jsonb_path_query_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query(fcinfo);
+}
+
+/*
+ * jsonb_path_query_array
+ * Executes jsonpath for given jsonb document and returns result as
+ * jsonb array.
+ */
+Datum
+jsonb_path_query_array(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
+}
+
+/*
+ * jsonb_path_query_array_novars
+ * Implements the 2-argument version of jsonb_path_query_array
+ */
+Datum
+jsonb_path_query_array_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_array(fcinfo);
+}
+
+/*
+ * jsonb_path_query_first
+ * Executes jsonpath for given jsonb document and returns first result
+ * item. If there are no items, NULL returned.
+ */
+Datum
+jsonb_path_query_first(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = { 0 };
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) >= 1)
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * jsonb_path_query_first_novars
+ * Implements the 2-argument version of jsonb_path_query_first
+ */
+Datum
+jsonb_path_query_first_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_first(fcinfo);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..cf648c3
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,493 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+accessor_expr:
+ path_primary { $$ = list_make1($1); }
+ | '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
+ | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
+ | accessor_expr accessor_op { $$ = lappend($1, $2); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..89e7f93
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scan.l
+ * Lexical parser for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a92..da13a87 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6..7287653 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..1c7af92 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,13 @@
{ oid => '3287', descr => 'delete path',
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
+{ oid => '6076', descr => 'jsonpath exists',
+ oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_exists(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6107', descr => 'jsonpath match',
+ oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_match(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ecc2e1..00e254b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,47 @@
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 => 'implementation of @? operator',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_novars' },
+{ oid => '6055', descr => 'jsonpath query without variables',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath',
+ prosrc => 'jsonb_path_query_novars' },
+{ oid => '6124', descr => 'jsonpath query without variables wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_array_novars' },
+{ oid => '6122', descr => 'jsonpath query without variables first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_first_novars' },
+{ oid => '6073', descr => 'implementation of @@ operator',
+ proname => 'jsonb_path_match', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_novars' },
+{ oid => '6056', descr => 'jsonpath exists test',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_exists' },
+{ oid => '6057', descr => 'jsonpath query',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query' },
+{ oid => '6125', descr => 'jsonpath query wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query_array' },
+{ oid => '6123', descr => 'jsonpath query first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_query_first' },
+{ oid => '6074', descr => 'jsonpath match', proname => 'jsonb_path_match',
+ prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_match' },
+
# 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 4b7750d..e44c562 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..4b1e80d 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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a..e0705e1 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9..bd1668c 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 6ccacf5..80962b7 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;
@@ -236,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,
};
/*
@@ -277,11 +286,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -379,5 +397,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 0000000..daef0c8
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,283 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.h
+ * Definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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 {
+ /* 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;
+ struct {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem {
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct {
+ uint32 first;
+ 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;
+ struct {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb)(void *, bool *);
+
+typedef struct JsonPathVariable {
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..419dc93
--- /dev/null
+++ b/src/include/utils/jsonpath_scanner.h
@@ -0,0 +1,31 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scanner.h
+ * Definitions for jsonpath scanner & parser
+ *
+ * Portions Copyright (c) 2019, 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 int jsonpath_yyparse(JsonPathParseResult **result);
+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
new file mode 100644
index 0000000..f8200d9
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1765 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'strict $[1]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonb_path_query
+------------------
+ 12
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonb_path_query
+------------------
+ {"a": 13}
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonb_path_query
+------------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonb_path_query
+------------------
+ 13
+ 14
+(2 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ jsonb_path_query
+------------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb_path_query('1', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[]', '$[last]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[last]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb_path_query('[1]', '$[last]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+ jsonb_path_query
+------------------
+ 2
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: Invalid SQL/JSON subscript
+select * from jsonb_path_query('{"a": 10}', '$');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+ERROR: could not find jsonpath variable 'value'
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+(1 row)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ jsonb_path_query
+------------------
+ 1
+ "2"
+(2 rows)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
+select jsonb_path_query('0', '1 / $');
+ERROR: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ jsonb_path_query
+------------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('2', '$ <= 1');
+ jsonb_path_query
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('2', '$ == "2"');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,2,3]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ jsonb_path_query
+------------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb_path_query('null', 'null.type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('null', 'true.type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('null', '123.type()');
+ jsonb_path_query
+------------------
+ "number"
+(1 row)
+
+select jsonb_path_query('null', '"123".type()');
+ jsonb_path_query
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+ jsonb_path_query
+------------------
+ 4
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: SQL/JSON array not found
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ jsonb_path_query
+------------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ jsonb_path_query
+------------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('{}', '$.keyvalue()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ jsonb_path_query
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('null', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('true', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('[]', '$.double()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('{}', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('1.23', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ jsonb_path_query
+------------------
+ null
+ 1
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+ jsonb_path_query
+------------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('true', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('1', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('""', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_array
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_array
+------------------------
+ [1]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ [2, 3]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_first
+------------------------
+ 2
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_first
+------------------------
+
+(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)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_exists
+-------------------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..a2143fd
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,788 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (@.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (exists (@.x))'::jsonpath;
+ jsonpath
+------------------------
+ $."g"?(exists (@."x"))
+(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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5..42e0c23 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 0c10c71..46433c8 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 0000000..c50fd13
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,395 @@
+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_path_query('[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_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonb_path_query('1', 'lax $[0]');
+select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('[1]', 'lax $[0]');
+select jsonb_path_query('[1]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+select jsonb_path_query('[]', '$[last]');
+select jsonb_path_query('[]', 'strict $[last]');
+select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+
+select * from jsonb_path_query('{"a": 10}', '$');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+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 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_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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 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_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+select jsonb_path_query('{"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)';
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonb_path_query('0', '1 / $');
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+select jsonb_path_query('2', '$ <= 1');
+select jsonb_path_query('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 jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select jsonb_path_query('null', 'null.type()');
+select jsonb_path_query('null', 'true.type()');
+select jsonb_path_query('null', '123.type()');
+select jsonb_path_query('null', '"123".type()');
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+select jsonb_path_query('{}', '$.keyvalue()');
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+
+select jsonb_path_query('null', '$.double()');
+select jsonb_path_query('true', '$.double()');
+select jsonb_path_query('[]', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()');
+select jsonb_path_query('{}', '$.double()');
+select jsonb_path_query('1.23', '$.double()');
+select jsonb_path_query('"1.23"', '$.double()');
+select jsonb_path_query('"1.23aaa"', '$.double()');
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..b548bc8
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,144 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? (exists (@.x))'::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;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1..0738c37 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 e4bb9d2..c46bae7 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-23 05:01 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-23 05:01 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Wed, Jan 23, 2019 at 3:40 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> Attached 26th version of the patches:
>
> * Documentation:
> - Fixed some mistakes
> - Removed mention of syntax extensions not present in this patch
> - Documented '.datetime(format, default_tz)'
> * Removed accidental syntax extension allowing to write '@.key' without '@'
Thank you!
I've made few more changes to patchset:
* Change back interface of JsonbExtractScalar(). I decide it would
be better to keep it.
* Run pgindent
* Improve documentation of .keyvalue() function.
Finally, I'm going to commit this if no objections.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (2 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v27.patch (application/octet-stream, 32.6K) download
commit 9498d13e218b5b49caf341be23987ee0e184a6a0
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Wed Jan 23 05:55:49 2019 +0300
Improve datetime conversion infrastructure for upcoming jsonpath
Jsonpath language (part of SQL/JSON standard) includes functions for datetime
conversion. In order to support that, we have to extend our infrastructure
in following ways.
1. FF1-FF6 format patterns implementing different fractions of second. FF3
and FF6 are effectively just synonyms for MS and US. But other fractions
were not implemented yet.
2. to_datetime() internal function, which dynamically determines result
datatype depending on format string.
3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
format patterns.
The first improvement is already user-visible and can use used in datetime
parsing/printing functions. Other improvements are internal, they will be
user-visible together with jsonpath.
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec17f62..6f5baefc177 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5993,6 +5993,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry><literal>US</literal></entry>
<entry>microsecond (000000-999999)</entry>
</row>
+ <row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
<row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a9785..dfe44533091 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 096d862c1de..fb1635854e7 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,19 +3052,22 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
{
FormatNode *n;
char *s;
int len,
value;
bool fx_mode = false;
+
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
@@ -3149,8 +3197,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3173,6 +3231,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
n->key->name)));
break;
case DCH_TZH:
+
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
@@ -3374,6 +3433,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3470,109 @@ DCH_prevent_counter_overflow(void)
}
}
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3861,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3881,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);
}
@@ -3718,7 +3902,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3739,11 +3923,176 @@ to_date(PG_FUNCTION_ARGS)
PG_RETURN_DATEADT(result);
}
+/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4100,15 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*/
static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4161,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4349,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a7e28..5103cd4b84a 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 bec129aff1c..bd15bfa5bb0 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 f5ec9bbd7e0..8a6f2cdb477 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 5b275dc9850..227779cf79b 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b45773339..74ecb7c10e6 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabddd9f..cb3dd190d7e 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c7199934..cdd3c1401ed 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd563ee..3c8580397ac 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cbb9aa..fea42550913 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c2331..588c3e033fa 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
0002-Jsonpath-engine-and-docs-v27.patch (application/octet-stream, 276.7K) download
commit 9c662e96ef63accce3d3db8d490979459f8ddef1
Author: Alexander Korotkov <akorotkov@postgresql.org>
Date: Wed Jan 23 05:56:55 2019 +0300
Implementation of JSON path language
SQL 2016 standards among other things contains set of SQL/JSON features for
JSON processing inside of relational database. The core of SQL/JSON is JSON
path language, allowing access parts of JSON documents and make computations
over them. This commit implements JSON path language as separate datatype
called "jsonpath".
Support of SQL/JSON features requires implementation of separate nodes, and it
will be considered in subsequent patches. This commit includes following
set of plain functions, allowing to execute jsonpath over jsonb values:
* jsonb_path_exists(jsonb, jsonpath[, jsonb]),
* jsonb_path_match(jsonb, jsonpath[, jsonb]),
* jsonb_path_query(jsonb, jsonpath[, jsonb]),
* jsonb_path_query_array(jsonb, jsonpath[, jsonb]).
* jsonb_path_query_first(jsonb, jsonpath[, jsonb]).
This commit also implements "jsonb @? jsonpath" and "jsonb @@ jsonpath", which
are wrappers over jsonpath_exists(jsonb, jsonpath) and jsonpath_predicate(jsonb,
jsonpath) correspondingly. These operators will have an index support
(implemented in subsequent patches).
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
diff --git a/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 49530241620..f06305d9dca 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6f5baefc177..d7a44455562 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,584 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>, <replaceable>default_tz</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template and default timezone</entry>
+ <entry><literal>["12:30 -02", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI TZH", "+03:00")</literal></entry>
+ <entry><literal>"12:30:00-02:00", "18:40:00+03:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>
+ Sequence of object's key-value pairs represented as array of objects
+ containing three fields (<literal>"key"</literal>,
+ <literal>"value"</literal>, and <literal>"id"</literal>).
+ <literal>"id"</literal> is an unique identifier of the object
+ key-value pair belongs to.
+ </entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20", "id": 0}, {"key": "y", "value": 32, "id": 0}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11900,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11909,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11917,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +11925,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,14 +11933,16 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
@@ -11503,6 +12067,18 @@ table2-mapping
JSON arrays, negative integers count from the end)</entry>
<entry><literal>'["a", {"b":1}]'::jsonb #- '{1,b}'</literal></entry>
</row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>Does JSON path returns any item for the specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)'</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@@</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>JSON path predicate check result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @@ '$.a[*] > 2'</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -11776,6 +12352,21 @@ table2-mapping
<indexterm>
<primary>jsonb_pretty</primary>
</indexterm>
+ <indexterm>
+ <primary>jsonb_path_exists</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_match</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_array</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_first</primary>
+ </indexterm>
<table id="functions-json-processing-table">
<title>JSON Processing Functions</title>
@@ -12110,6 +12701,159 @@ table2-mapping
</programlisting>
</entry>
</row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Checks whether JSON path returns any item for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Returns JSON path predicate result for the specified JSON value.
+ Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', '$.a[*] > 2')
+ </literal></para>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>setof jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)');
+ </literal></para>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}');
+ </literal></para>
+ </entry>
+ <entry>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 3
+ 4
+ 5
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 2
+ 3
+ 4
+ </programlisting>
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value and wraps result into an array. Variables are substituted to
+ JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>[3, 4, 5]</literal></para>
+ <para><literal>[2, 3, 4]</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets the first JSON item returned by JSON path for the specified JSON
+ value. Returns <literal>NULL</literal> on no results. Variables are
+ substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>3</literal></para>
+ <para><literal>2</literal></para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12891,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +12946,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa0d24..12a707369f6 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -535,6 +548,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
therefore ill-suited for applications that often perform such searches.
</para>
+ <para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@@</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
<para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96db9bc..31d9d6605d9 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
@@ -308,6 +316,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/utils/adt/.gitignore b/src/backend/utils/adt/.gitignore
new file mode 100644
index 00000000000..7fab054407e
--- /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 20eead17983..d335eec09a4 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/json.c b/src/backend/utils/adt/json.c
index de0d0723b71..5239903a831 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without
+ * time-zone, and then use specified time-zone for encoding
+ * timestamp into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c02c8569f28..f27fd0a746c 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:
@@ -1857,7 +1857,7 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
/*
* Extract scalar value from raw-scalar pseudo-array jsonb.
*/
-static bool
+bool
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
{
JsonbIterator *it;
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363a4b0..c4bb7c8a4e7 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,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 00000000000..f4eaaa8e5ba
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,898 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.c
+ * Input/output and supporting routines for jsonpath
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+static 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;
+ }
+}
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem * item,
+ 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();
+
+ appendStringInfoChar(buf, (char) (item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char *) item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char *) &item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char *) &arg /* fake value */ , sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char *) &item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char *) &item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *) (buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */ );
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4);
+ break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4);
+ 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13);
+ 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem * v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf, "exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */ );
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem * v, JsonPath * js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem * v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch (v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem * v, JsonPathItem * a)
+{
+ Assert(v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus);
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem * v, JsonPathItem * a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith);
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith);
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem * v, JsonPathItem * a)
+{
+ Assert(v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith);
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem * v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool) *v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem * v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric) v->content.value.data;
+}
+
+char *
+jspGetString(JsonPathItem * v, int32 *len)
+{
+ Assert(v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable);
+
+ if (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
new file mode 100644
index 00000000000..2721a41b9f0
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2855 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_exec.c
+ * Routines for SQL/JSON path execution.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#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"
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+
+typedef struct JsonBaseObjectInfo
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext * cxt,
+ JsonPathItem * jsp, JsonbValue *jb,
+ JsonValueList * found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext * cxt,
+ JsonPathItem * jsp, JsonbValue *jb, JsonValueList * found);
+
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*out));
+
+ return JsonbInitBinary(out, jb);
+}
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+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;
+}
+
+static inline JsonbValue *
+extractScalar(JsonbValue *scalar, JsonbValue *buf)
+{
+ if (scalar->type == jbvBinary &&
+ JsonContainerIsScalar(scalar->val.binary.data))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(scalar->val.binary.data, buf);
+ Assert(res);
+ return buf;
+ }
+
+ return scalar;
+}
+
+static inline JsonbValue *
+getScalar(JsonbValue *scalar, JsonbValue *buf, int type)
+{
+ scalar = extractScalar(scalar, buf);
+
+ return scalar->type == type ? scalar : NULL;
+}
+
+/* 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)))
+ {
+ JsonbValue bin;
+
+ jbv = extractScalar(jbv, jbv);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &bin);
+
+ pushJsonbValue(&ps, WJB_ELEM, jbv);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
+
+/********************Execute functions for JsonPath***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem * variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(&jb->root, value);
+ Assert(res);
+ }
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value.
+ *
+ * If node is a variable then its id returned, otherwise 0 returned.
+ */
+static int
+computeJsonPathItem(JsonPathExecContext * cxt, JsonPathItem * item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "unexpected jsonpath item type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(jbc, &jbvbuf);
+ Assert(res);
+ jb = &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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Compare two SQL/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+
+ /*
+ * Equality and order comparison of nulls to non-nulls returns
+ * always false, but inequality comparison returns true.
+ */
+ return op == jpiNotEqual ? jpbTrue : jpbFalse;
+
+ /* Non-null items of different types are not comparable. */
+ return jpbUnknown;
+ }
+
+ switch (jb1->type)
+ {
+ case jbvNull:
+ cmp = 0;
+ break;
+ case jbvBool:
+ cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
+ jb1->val.boolean ? 1 : -1;
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+
+ case jbvBinary:
+ case jbvArray:
+ case jbvObject:
+ return jpbUnknown; /* non-scalars are not comparable */
+
+ default:
+ elog(ERROR, "invalid jsonb value type %d", jb1->type);
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+/* Comparison predicate callback. */
+static inline JsonPathBool
+executeComparison(JsonPathItem * cmp, JsonbValue *lv, JsonbValue *rv, void *p)
+{
+ return compareItems(cmp->type, lv, rv);
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext * cxt,
+ JsonPathItem * cur, JsonPathItem * next,
+ JsonbValue *v, JsonValueList * 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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = {0};
+ JsonValueListIterator it = {0};
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ return recursiveExecute(cxt, jsp, jb, found);
+}
+
+typedef JsonPathBool(*JsonPathPredicateCallback) (JsonPathItem * jsp,
+ JsonbValue *larg,
+ JsonbValue *rarg,
+ void *param);
+
+/*
+ * Execute unary or binary predicate.
+ *
+ * Predicates have existence semantics, because their operands are item
+ * sequences. Pairs of items from the left and right operand's sequences are
+ * checked. TRUE returned only if any pair satisfying the condition is found.
+ * In strict mode, even if the desired pair has already been found, all pairs
+ * still need to be examined to check the absence of errors. If any error
+ * occurs, UNKNOWN (analogous to SQL NULL) is returned.
+ */
+static inline JsonPathBool
+executePredicate(JsonPathExecContext * cxt, JsonPathItem * pred,
+ JsonPathItem * larg, JsonPathItem * rarg, JsonbValue *jb,
+ bool unwrapRightArg, JsonPathPredicateCallback exec, void *param)
+{
+ JsonPathExecResult res;
+ JsonValueListIterator lseqit = {0};
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ /* Left argument is always auto-unwrapped. */
+ res = recursiveExecuteAndUnwrap(cxt, larg, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (rarg)
+ {
+ /* Right argument is conditionally auto-unwrapped. */
+ res = unwrapRightArg ?
+ recursiveExecuteAndUnwrap(cxt, rarg, jb, &rseq) :
+ recursiveExecute(cxt, rarg, jb, &rseq);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+ }
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit;
+ JsonbValue *rval;
+ int i = 0;
+
+ if (rarg)
+ memset(&rseqit, 0, sizeof(rseqit));
+ else
+ rval = NULL;
+
+ /* Loop only if we have right arg sequence. */
+ while (rarg ? !!(rval = JsonValueListNext(&rseq, &rseqit)) : !i++)
+ {
+ JsonPathBool res = exec(pred, lval, rval, param);
+
+ if (res == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (res == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * Execute binary arithmetic expression on singleton numeric operands.
+ * Array operands are automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeBinaryArithmExpr(JsonPathExecContext * cxt, JsonPathItem * jsp,
+ JsonbValue *jb, JsonValueList * found)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1 ||
+ !(lval = getScalar(JsonValueListHead(&lseq), &lvalbuf, jbvNumeric)) ||
+ !(rval = getScalar(JsonValueListHead(&rseq), &rvalbuf, jbvNumeric)))
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
+ * no function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = {0};
+ JsonValueListIterator it = {0};
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if ((val = getScalar(val, val, jbvNumeric)))
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else
+ {
+ if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+ }
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext * cxt, JsonPathItem * jsp, JsonbValue *jb,
+ JsonValueList * 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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = {0};
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1 ||
+ !(jbv = getScalar(JsonValueListHead(&found), &tmp, jbvNumeric)))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ *index = DatumGetInt32(DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(jbv->val.numeric),
+ Int32GetDatum(0))));
+
+ return jperOk;
+}
+
+/*
+ * STARTS_WITH predicate callback.
+ *
+ * Check if the 'whole' string starts from 'initial' string.
+ */
+static inline JsonPathBool
+executeStartsWith(JsonPathItem * jsp, JsonbValue *whole, JsonbValue *initial,
+ void *param)
+{
+ JsonbValue wholeBuf;
+ JsonbValue initialBuf;
+
+ if (!(whole = getScalar(whole, &wholeBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (!(initial = getScalar(initial, &initialBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (whole->val.string.len >= initial->val.string.len &&
+ !memcmp(whole->val.string.val,
+ initial->val.string.val,
+ initial->val.string.len))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/* Context for LIKE_REGEX execution. */
+typedef struct LikeRegexContext
+{
+ text *regex;
+ int cflags;
+} LikeRegexContext;
+
+/*
+ * LIKE_REGEX predicate callback.
+ *
+ * Check if the string matches regex pattern.
+ */
+static JsonPathBool
+executeLikeRegex(JsonPathItem * jsp, JsonbValue *str, JsonbValue *rarg,
+ void *param)
+{
+ LikeRegexContext *cxt = param;
+ JsonbValue strbuf;
+
+ if (!(str = getScalar(str, &strbuf, jbvString)))
+ return jpbUnknown;
+
+ /* Cache regex text and converted flags. */
+ if (!cxt->regex)
+ {
+ uint32 flags = jsp->content.like_regex.flags;
+
+ cxt->regex =
+ cstring_to_text_with_len(jsp->content.like_regex.pattern,
+ jsp->content.like_regex.patternlen);
+
+ /* Convert regex flags. */
+ cxt->cflags = REG_ADVANCED;
+
+ if (flags & JSP_REGEX_ICASE)
+ cxt->cflags |= REG_ICASE;
+ if (flags & JSP_REGEX_MLINE)
+ cxt->cflags |= REG_NEWLINE;
+ if (flags & JSP_REGEX_SLINE)
+ cxt->cflags &= ~REG_NEWLINE;
+ if (flags & JSP_REGEX_WSPACE)
+ cxt->cflags |= REG_EXPANDED;
+ }
+
+ if (RE_compile_and_execute(cxt->regex, str->val.string.val,
+ str->val.string.len,
+ cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
+ * no function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 larg;
+ JsonPathItem rarg;
+ 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, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbFalse)
+ return jpbFalse;
+
+ /*
+ * SQL/JSON says that we should check second arg in case of
+ * jperError
+ */
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbTrue ? res : res2;
+
+ case jpiOr:
+ jspGetLeftArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbTrue)
+ return jpbTrue;
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbFalse ? res : res2;
+
+ case jpiNot:
+ jspGetArg(jsp, &larg);
+
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbUnknown)
+ return jpbUnknown;
+
+ return res == jpbTrue ? jpbFalse : jpbTrue;
+
+ case jpiIsUnknown:
+ jspGetArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+ return res == jpbUnknown ? jpbTrue : jpbFalse;
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ jspGetLeftArg(jsp, &larg);
+ jspGetRightArg(jsp, &rarg);
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
+ executeComparison, NULL);
+
+ case jpiStartsWith: /* 'whole STARTS WITH initial' */
+ jspGetLeftArg(jsp, &larg); /* 'whole' */
+ jspGetRightArg(jsp, &rarg); /* 'initial' */
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
+ executeStartsWith, NULL);
+
+ case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ {
+ /*
+ * 'expr' is a sequence-returning expression. 'pattern' is a
+ * regex string literal. SQL/JSON standard requires XQuery
+ * regexes, but we use Postgres regexes here. 'flags' is a
+ * string literal converted to integer flags at compile-time.
+ */
+ LikeRegexContext lrcxt = {0};
+
+ jspInitByBuffer(&larg, jsp->base, jsp->content.like_regex.expr);
+
+ return executePredicate(cxt, jsp, &larg, NULL, jb, false,
+ executeLikeRegex, &lrcxt);
+ }
+
+ case jpiExists:
+ jspGetArg(jsp, &larg);
+
+ 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, &larg, jb, &vals);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &larg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+/*
+ * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
+ * item onto the stack.
+ */
+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)
+{
+ JsonbValue *v;
+ JsonbValue vbuf;
+ bool copy = true;
+
+ if (JsonbType(jbv) == jbvScalar)
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ if (jspHasNext(jsp))
+ v = &vbuf;
+ else
+ {
+ v = palloc(sizeof(*v));
+ copy = false;
+ }
+
+ res = JsonbExtractScalar(jbv->val.binary.data, v);
+ Assert(res);
+ }
+ else
+ v = jbv;
+
+ return recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+}
+
+/* Save base object and its id needed for the execution of .keyvalue(). */
+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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext * cxt, JsonPathItem * jsp,
+ JsonbValue *jb, JsonValueList * found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ break;
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ {
+ JsonbValue jbvbuf;
+
+ if ((jb = getScalar(jb, &jbvbuf, 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ MemoryContext mcxt = CurrentMemoryContext;
+
+ jb = extractScalar(jb, &jbv);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without
+ * subtransaction because no function called inside performs
+ * data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz;
+ bool hasNext;
+
+ res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
+
+ if (!(jb = getScalar(jb, &jbvbuf, jbvString)))
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = {0};
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = {0};
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+
+ /*
+ * .keyvalue() method returns a sequence of object's key-value
+ * pairs in the following format: '{ "key": key, "value": value,
+ * "id": id }'.
+ *
+ * "id" field is an object identifier which is constructed from
+ * the two parts: base object id and its binary offset in base
+ * object's jsonb: id = 10000000000 * base_object_id +
+ * obj_offset_in_base_object
+ *
+ * 10000000000 (10^10) -- is a first round decimal number greater
+ * than 2^32 (maximal offset in jsonb). Decimal multiplier is
+ * used here to improve the readability of identifiers.
+ *
+ * Base object is usually a root object of the path: context item
+ * '$' or path variable '$var', literals can't produce objects for
+ * now. But if the path contains generated objects (.keyvalue()
+ * itself, for example), then they become base object for the
+ * subsequent .keyvalue().
+ *
+ * Id of '$' is 0. Id of '$var' is its ordinal (positive) number
+ * in the list of variables (see computeJsonPathVariable()). Ids
+ * for generated objects are assigned using global counter
+ * JsonPathExecContext.generatedObjectId starting from
+ * (number_of_vars + 1).
+ */
+
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ idstr.type = jbvString;
+ idstr.val.string.val = "id";
+ idstr.val.string.len = 2;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ /* construct object id from its base object and offset in it */
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext * cxt, JsonPathItem * jsp, JsonbValue *jb,
+ JsonValueList * found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Interface to jsonpath executor
+ */
+static JsonPathExecResult
+executeJsonPath(JsonPath * path, List *vars, Jsonb *json, JsonValueList * foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+/*
+ * jsonb_path_exists
+ * Returns true if jsonpath returns at least one item for the specified
+ * jsonb value.
+ */
+Datum
+jsonb_path_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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+/*
+ * jsonb_path_exists_novars
+ * Implements the 2-argument version of jsonb_path_exists
+ */
+Datum
+jsonb_path_exists_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_exists(fcinfo);
+}
+
+/*
+ * jsonb_path_match
+ * Returns jsonpath predicate result item for the specified jsonb value.
+ * The result of jsonpath execution should be a single item, error is
+ * raised otherwise. Non-bool result is treated as NULL.
+ */
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonbValue *jbv;
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) != 1)
+ throwJsonPathError(jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED));
+
+ jbv = JsonValueListHead(&found);
+ jbv = extractScalar(jbv, 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);
+}
+
+/*
+ * jsonb_path_match_novars
+ * Implements the 2-argument version of jsonb_path_match
+ */
+Datum
+jsonb_path_match_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_match(fcinfo);
+}
+
+/*
+ * jsonb_path_query
+ * Executes jsonpath for given jsonb document and returns result as
+ * rowset.
+ */
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = {0};
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+/*
+ * jsonb_path_query_novars
+ * Implements the 2-argument version of jsonb_path_query
+ */
+Datum
+jsonb_path_query_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query(fcinfo);
+}
+
+/*
+ * jsonb_path_query_array
+ * Executes jsonpath for given jsonb document and returns result as
+ * jsonb array.
+ */
+Datum
+jsonb_path_query_array(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
+}
+
+/*
+ * jsonb_path_query_array_novars
+ * Implements the 2-argument version of jsonb_path_query_array
+ */
+Datum
+jsonb_path_query_array_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_array(fcinfo);
+}
+
+/*
+ * jsonb_path_query_first
+ * Executes jsonpath for given jsonb document and returns first result
+ * item. If there are no items, NULL returned.
+ */
+Datum
+jsonb_path_query_first(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) >= 1)
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * jsonb_path_query_first_novars
+ * Implements the 2-argument version of jsonb_path_query_first
+ */
+Datum
+jsonb_path_query_first_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_first(fcinfo);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 00000000000..cf648c368d8
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,493 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+accessor_expr:
+ path_primary { $$ = list_make1($1); }
+ | '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
+ | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
+ | accessor_expr accessor_op { $$ = lappend($1, $2); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 00000000000..89e7f938f36
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scan.l
+ * Lexical parser for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a9290ae..da13a875eb0 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6e5c9..72876533acf 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec0780b9..1c7af92eb19 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,13 @@
{ oid => '3287', descr => 'delete path',
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
+{ oid => '6076', descr => 'jsonpath exists',
+ oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_exists(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6107', descr => 'jsonpath match',
+ oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_match(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ecc2e12c3f..00e254bb084 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,47 @@
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 => 'implementation of @? operator',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_novars' },
+{ oid => '6055', descr => 'jsonpath query without variables',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath',
+ prosrc => 'jsonb_path_query_novars' },
+{ oid => '6124', descr => 'jsonpath query without variables wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_array_novars' },
+{ oid => '6122', descr => 'jsonpath query without variables first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_first_novars' },
+{ oid => '6073', descr => 'implementation of @@ operator',
+ proname => 'jsonb_path_match', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_novars' },
+{ oid => '6056', descr => 'jsonpath exists test',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_exists' },
+{ oid => '6057', descr => 'jsonpath query',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query' },
+{ oid => '6125', descr => 'jsonpath query wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query_array' },
+{ oid => '6123', descr => 'jsonpath query first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_query_first' },
+{ oid => '6074', descr => 'jsonpath match', proname => 'jsonb_path_match',
+ prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_match' },
+
# 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 4b7750d4398..e44c562218d 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc090409..23ad03d9304 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -167,10 +167,18 @@ typedef struct
/*
* the prototypes for exported functions
*/
+
+/* regcomp.c */
extern int pg_regcomp(regex_t *, const pg_wchar *, size_t, int, Oid);
extern int pg_regexec(regex_t *, const pg_wchar *, size_t, size_t, rm_detail_t *, size_t, regmatch_t[], int);
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);
+/* regexp.c */
+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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a8d6c..e0705e1aa70 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9aad4..9eda9a3e00a 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 6ccacf545ce..55c8d6a375c 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;
@@ -236,7 +238,15 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+
+ /*
+ * Virtual types.
+ *
+ * These types are used only for in-memory JSON processing and serialized
+ * into JSON strings when outputted to json/jsonb.
+ */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +287,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -378,6 +397,6 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
int estimated_len);
-
+extern bool 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 00000000000..6ecaf03dcf5
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,300 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.h
+ * Definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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
+ {
+ /* 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;
+ struct
+ {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct
+ {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem * v, JsonPathItem * from,
+ JsonPathItem * to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem
+{
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct
+ {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct
+ {
+ uint32 first;
+ 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;
+ struct
+ {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb) (void *, bool *);
+
+typedef struct JsonPathVariable
+{
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 00000000000..0dfd9c3f338
--- /dev/null
+++ b/src/include/utils/jsonpath_scanner.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scanner.h
+ * Definitions for jsonpath scanner & parser
+ *
+ * Portions Copyright (c) 2019, 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 int jsonpath_yyparse(JsonPathParseResult * *result);
+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
new file mode 100644
index 00000000000..f8200d92fc0
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1765 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'strict $[1]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonb_path_query
+------------------
+ 12
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonb_path_query
+------------------
+ {"a": 13}
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonb_path_query
+------------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonb_path_query
+------------------
+ 13
+ 14
+(2 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ jsonb_path_query
+------------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb_path_query('1', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[]', '$[last]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[last]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb_path_query('[1]', '$[last]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+ jsonb_path_query
+------------------
+ 2
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: Invalid SQL/JSON subscript
+select * from jsonb_path_query('{"a": 10}', '$');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+ERROR: could not find jsonpath variable 'value'
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+(1 row)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ jsonb_path_query
+------------------
+ 1
+ "2"
+(2 rows)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
+select jsonb_path_query('0', '1 / $');
+ERROR: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ jsonb_path_query
+------------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('2', '$ <= 1');
+ jsonb_path_query
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('2', '$ == "2"');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,2,3]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ jsonb_path_query
+------------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb_path_query('null', 'null.type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('null', 'true.type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('null', '123.type()');
+ jsonb_path_query
+------------------
+ "number"
+(1 row)
+
+select jsonb_path_query('null', '"123".type()');
+ jsonb_path_query
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+ jsonb_path_query
+------------------
+ 4
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: SQL/JSON array not found
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ jsonb_path_query
+------------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ jsonb_path_query
+------------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('{}', '$.keyvalue()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ jsonb_path_query
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('null', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('true', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('[]', '$.double()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('{}', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('1.23', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ jsonb_path_query
+------------------
+ null
+ 1
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+ jsonb_path_query
+------------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('true', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('1', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('""', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_array
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_array
+------------------------
+ [1]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ [2, 3]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_first
+------------------------
+ 2
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_first
+------------------------
+
+(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)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_exists
+-------------------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 00000000000..a2143fd575c
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,788 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (@.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (exists (@.x))'::jsonpath;
+ jsonpath
+------------------------
+ $."g"?(exists (@."x"))
+(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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5db9f..42e0c235956 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 0c10c7100c6..46433c85838 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 00000000000..c50fd131db9
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,395 @@
+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_path_query('[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_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonb_path_query('1', 'lax $[0]');
+select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('[1]', 'lax $[0]');
+select jsonb_path_query('[1]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+select jsonb_path_query('[]', '$[last]');
+select jsonb_path_query('[]', 'strict $[last]');
+select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+
+select * from jsonb_path_query('{"a": 10}', '$');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+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 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_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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 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_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+select jsonb_path_query('{"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)';
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonb_path_query('0', '1 / $');
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+select jsonb_path_query('2', '$ <= 1');
+select jsonb_path_query('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 jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select jsonb_path_query('null', 'null.type()');
+select jsonb_path_query('null', 'true.type()');
+select jsonb_path_query('null', '123.type()');
+select jsonb_path_query('null', '"123".type()');
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+select jsonb_path_query('{}', '$.keyvalue()');
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+
+select jsonb_path_query('null', '$.double()');
+select jsonb_path_query('true', '$.double()');
+select jsonb_path_query('[]', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()');
+select jsonb_path_query('{}', '$.double()');
+select jsonb_path_query('1.23', '$.double()');
+select jsonb_path_query('"1.23"', '$.double()');
+select jsonb_path_query('"1.23aaa"', '$.double()');
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 00000000000..b548bc8512c
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,144 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? (exists (@.x))'::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;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1b20c..0738c37c705 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 e4bb9d2394d..c46bae7fb66 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-23 05:02 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Oleg Bartunov <obartunov@postgrespro.ru>
1 sibling, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-23 05:02 UTC (permalink / raw)
To: Oleg Bartunov <obartunov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 22, 2019 at 12:13 PM Oleg Bartunov <obartunov@postgrespro.ru> wrote:
>
> On Tue, Jan 22, 2019 at 8:21 AM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> >
> > The next revision is attached.
> >
> > Nikita made code and documentation improvements, renamed functions
> > from "jsonpath_" prefix to "jsonb_path_" prefix. He also renamed
> > jsonpath_predicate() to jsonb_path_match() (that looks better for me
> > too).
> >
> > I've further renamed jsonb_query_wrapped() to jsonb_query_array(), and
> > changed that behavior to always wrap result into array.
>
> agree with new names
>
> so it mimic the behaviour of JSON_QUERY with UNCONDITIONAL WRAPPER option
Good, thank you for feedback!
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-23 12:25 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2019-01-23 12:25 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Attached 28th version of the patches.
On 23.01.2019 8:01, Alexander Korotkov wrote:
> On Wed, Jan 23, 2019 at 3:40 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
>> Attached 26th version of the patches:
>>
>> * Documentation:
>> - Fixed some mistakes
>> - Removed mention of syntax extensions not present in this patch
>> - Documented '.datetime(format, default_tz)'
>> * Removed accidental syntax extension allowing to write '@.key' without '@'
> Thank you!
>
> I've made few more changes to patchset:
> * Change back interface of JsonbExtractScalar(). I decide it would
> be better to keep it.
> * Run pgindent
I have fixed indentation by adding new typedefs to
src/tools/pgindent/typedefs.list.
Also I have renamed LikeRegexContext => JsonLikeRegexContext.
> * Improve documentation of .keyvalue() function.
>
> Finally, I'm going to commit this if no objections.
Thank you!
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments: download all (2 files, .tar.gz)
0001-Preliminary-datetime-infrastructure-v28.patch (text/x-patch, 33.1K) download
From 66ff10ecca4d2437fec98696dde7e1b81565c47e Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 23 Jan 2019 13:21:40 +0300
Subject: [PATCH 1/2] Improve datetime conversion infrastructure for upcoming
jsonpath
Jsonpath language (part of SQL/JSON standard) includes functions for datetime
conversion. In order to support that, we have to extend our infrastructure
in following ways.
1. FF1-FF6 format patterns implementing different fractions of second. FF3
and FF6 are effectively just synonyms for MS and US. But other fractions
were not implemented yet.
2. to_datetime() internal function, which dynamically determines result
datatype depending on format string.
3. Strict parsing mode, which doesn't allow trailing spaces and unmatched
format patterns.
The first improvement is already user-visible and can use used in datetime
parsing/printing functions. Other improvements are internal, they will be
user-visible together with jsonpath.
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
---
doc/src/sgml/func.sgml | 24 ++
src/backend/utils/adt/date.c | 11 +-
src/backend/utils/adt/formatting.c | 408 ++++++++++++++++++++++++++++--
src/backend/utils/adt/timestamp.c | 3 +-
src/include/utils/date.h | 3 +
src/include/utils/datetime.h | 2 +
src/include/utils/formatting.h | 3 +
src/test/regress/expected/horology.out | 79 ++++++
src/test/regress/expected/timestamp.out | 15 ++
src/test/regress/expected/timestamptz.out | 15 ++
src/test/regress/sql/horology.sql | 9 +
src/test/regress/sql/timestamp.sql | 8 +
src/test/regress/sql/timestamptz.sql | 8 +
13 files changed, 554 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 4930ec1..6f5baef 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5994,6 +5994,30 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<entry>microsecond (000000-999999)</entry>
</row>
<row>
+ <entry><literal>FF1</literal></entry>
+ <entry>decisecond (0-9)</entry>
+ </row>
+ <row>
+ <entry><literal>FF2</literal></entry>
+ <entry>centisecond (00-99)</entry>
+ </row>
+ <row>
+ <entry><literal>FF3</literal></entry>
+ <entry>millisecond (000-999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF4</literal></entry>
+ <entry>tenth of a millisecond (0000-9999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF5</literal></entry>
+ <entry>hundredth of a millisecond (00000-99999)</entry>
+ </row>
+ <row>
+ <entry><literal>FF6</literal></entry>
+ <entry>microsecond (000000-999999)</entry>
+ </row>
+ <row>
<entry><literal>SSSS</literal></entry>
<entry>seconds past midnight (0-86399)</entry>
</row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 3810e4a..dfe4453 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 096d862..fb16358 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -86,6 +86,7 @@
#endif
#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/date.h"
@@ -436,7 +437,8 @@ typedef struct
clock, /* 12 or 24 hour clock? */
tzsign, /* +1, -1 or 0 if timezone info is absent */
tzh,
- tzm;
+ tzm,
+ ff; /* fractional precision */
} TmFromChar;
#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))
@@ -596,6 +598,12 @@ typedef enum
DCH_Day,
DCH_Dy,
DCH_D,
+ DCH_FF1,
+ DCH_FF2,
+ DCH_FF3,
+ DCH_FF4,
+ DCH_FF5,
+ DCH_FF6,
DCH_FX, /* global suffix */
DCH_HH24,
DCH_HH12,
@@ -645,6 +653,12 @@ typedef enum
DCH_dd,
DCH_dy,
DCH_d,
+ DCH_ff1,
+ DCH_ff2,
+ DCH_ff3,
+ DCH_ff4,
+ DCH_ff5,
+ DCH_ff6,
DCH_fx,
DCH_hh24,
DCH_hh12,
@@ -745,7 +759,13 @@ static const KeyWord DCH_keywords[] = {
{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* F */
+ {"FF2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"FF3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"FF4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"FF5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"FF6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -794,7 +814,13 @@ static const KeyWord DCH_keywords[] = {
{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
- {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff1", 3, DCH_FF1, false, FROM_CHAR_DATE_NONE}, /* f */
+ {"ff2", 3, DCH_FF2, false, FROM_CHAR_DATE_NONE},
+ {"ff3", 3, DCH_FF3, false, FROM_CHAR_DATE_NONE},
+ {"ff4", 3, DCH_FF4, false, FROM_CHAR_DATE_NONE},
+ {"ff5", 3, DCH_FF5, false, FROM_CHAR_DATE_NONE},
+ {"ff6", 3, DCH_FF6, false, FROM_CHAR_DATE_NONE},
+ {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
@@ -895,10 +921,10 @@ static const int DCH_index[KeyWord_INDEX_SIZE] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
- DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
+ DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
- DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
+ DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
-1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww,
-1, DCH_y_yyy, -1, -1, -1, -1
@@ -962,6 +988,10 @@ typedef struct NUMProc
*L_currency_symbol;
} NUMProc;
+/* Return flags for DCH_from_char() */
+#define DCH_DATED 0x01
+#define DCH_TIMED 0x02
+#define DCH_ZONED 0x04
/* ----------
* Functions
@@ -977,7 +1007,8 @@ static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
static void DCH_to_char(FormatNode *node, bool is_interval,
TmToChar *in, char *out, Oid collid);
-static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out);
+static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out,
+ bool strict);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(const KeyWord *k, const int *index);
@@ -994,8 +1025,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,
- struct pg_tm *tm, fsec_t *fsec);
+static void do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree);
static char *int_to_roman(int number);
@@ -2514,18 +2545,32 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
- case DCH_MS: /* millisecond */
- sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000)));
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
+#define DCH_to_char_fsec(frac_fmt, frac_val) \
+ sprintf(s, frac_fmt, (int) (frac_val)); \
+ if (S_THth(n->suffix)) \
+ str_numth(s, s, S_TH_TYPE(n->suffix)); \
s += strlen(s);
+ case DCH_FF1: /* decisecond */
+ DCH_to_char_fsec("%01d", in->fsec / 100000);
+ break;
+ case DCH_FF2: /* centisecond */
+ DCH_to_char_fsec("%02d", in->fsec / 10000);
+ break;
+ case DCH_FF3:
+ case DCH_MS: /* millisecond */
+ DCH_to_char_fsec("%03d", in->fsec / 1000);
+ break;
+ case DCH_FF4:
+ DCH_to_char_fsec("%04d", in->fsec / 100);
break;
+ case DCH_FF5:
+ DCH_to_char_fsec("%05d", in->fsec / 10);
+ break;
+ case DCH_FF6:
case DCH_US: /* microsecond */
- sprintf(s, "%06d", (int) in->fsec);
- if (S_THth(n->suffix))
- str_numth(s, s, S_TH_TYPE(n->suffix));
- s += strlen(s);
+ DCH_to_char_fsec("%06d", in->fsec);
break;
+#undef DCH_to_char_fsec
case DCH_SSSS:
sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
tm->tm_min * SECS_PER_MINUTE +
@@ -3007,19 +3052,22 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
/* ----------
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* ----------
*/
static void
-DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
+DCH_from_char(FormatNode *node, char *in, TmFromChar *out, bool strict)
{
FormatNode *n;
char *s;
int len,
value;
bool fx_mode = false;
+
/* number of extra skipped characters (more than given in format string) */
int extra_skip = 0;
@@ -3149,8 +3197,18 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
SKIP_THth(s, n->suffix);
break;
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ out->ff = n->key->id - DCH_FF1 + 1;
+ /* fall through */
case DCH_US: /* microsecond */
- len = from_char_parse_int_len(&out->us, &s, 6, n);
+ len = from_char_parse_int_len(&out->us, &s,
+ n->key->id == DCH_US ? 6 :
+ out->ff, n);
out->us *= len == 1 ? 100000 :
len == 2 ? 10000 :
@@ -3173,6 +3231,7 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
n->key->name)));
break;
case DCH_TZH:
+
/*
* Value of TZH might be negative. And the issue is that we
* might swallow minus sign as the separator. So, if we have
@@ -3374,6 +3433,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
}
}
}
+
+ if (strict)
+ {
+ if (n->type != NODE_TYPE_END)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("input string is too short for datetime format")));
+
+ while (*s != '\0' && isspace((unsigned char) *s))
+ s++;
+
+ if (*s != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("trailing characters remain in input string after "
+ "datetime format")));
+ }
}
/*
@@ -3394,6 +3470,109 @@ DCH_prevent_counter_overflow(void)
}
}
+/* Get mask of date/time/zone components present in format nodes. */
+static int
+DCH_datetime_type(FormatNode *node)
+{
+ FormatNode *n;
+ int flags = 0;
+
+ for (n = node; n->type != NODE_TYPE_END; n++)
+ {
+ if (n->type != NODE_TYPE_ACTION)
+ continue;
+
+ switch (n->key->id)
+ {
+ case DCH_FX:
+ break;
+ case DCH_A_M:
+ case DCH_P_M:
+ case DCH_a_m:
+ case DCH_p_m:
+ case DCH_AM:
+ case DCH_PM:
+ case DCH_am:
+ case DCH_pm:
+ case DCH_HH:
+ case DCH_HH12:
+ case DCH_HH24:
+ case DCH_MI:
+ case DCH_SS:
+ case DCH_MS: /* millisecond */
+ case DCH_US: /* microsecond */
+ case DCH_FF1:
+ case DCH_FF2:
+ case DCH_FF3:
+ case DCH_FF4:
+ case DCH_FF5:
+ case DCH_FF6:
+ case DCH_SSSS:
+ flags |= DCH_TIMED;
+ break;
+ case DCH_tz:
+ case DCH_TZ:
+ case DCH_OF:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("formatting field \"%s\" is only supported in to_char",
+ n->key->name)));
+ flags |= DCH_ZONED;
+ break;
+ case DCH_TZH:
+ case DCH_TZM:
+ flags |= DCH_ZONED;
+ break;
+ case DCH_A_D:
+ case DCH_B_C:
+ case DCH_a_d:
+ case DCH_b_c:
+ case DCH_AD:
+ case DCH_BC:
+ case DCH_ad:
+ case DCH_bc:
+ case DCH_MONTH:
+ case DCH_Month:
+ case DCH_month:
+ case DCH_MON:
+ case DCH_Mon:
+ case DCH_mon:
+ case DCH_MM:
+ case DCH_DAY:
+ case DCH_Day:
+ case DCH_day:
+ case DCH_DY:
+ case DCH_Dy:
+ case DCH_dy:
+ case DCH_DDD:
+ case DCH_IDDD:
+ case DCH_DD:
+ case DCH_D:
+ case DCH_ID:
+ case DCH_WW:
+ case DCH_Q:
+ case DCH_CC:
+ case DCH_Y_YYY:
+ case DCH_YYYY:
+ case DCH_IYYY:
+ case DCH_YYY:
+ case DCH_IYY:
+ case DCH_YY:
+ case DCH_IY:
+ case DCH_Y:
+ case DCH_I:
+ case DCH_RM:
+ case DCH_rm:
+ case DCH_W:
+ case DCH_J:
+ flags |= DCH_DATED;
+ break;
+ }
+ }
+
+ return flags;
+}
+
/* select a DCHCacheEntry to hold the given format picture */
static DCHCacheEntry *
DCH_cache_getnew(const char *str)
@@ -3682,8 +3861,9 @@ to_timestamp(PG_FUNCTION_ARGS)
int tz;
struct pg_tm tm;
fsec_t fsec;
+ int fprec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, &fprec, NULL);
/* Use the specified time zone, if any. */
if (tm.tm_zone)
@@ -3701,6 +3881,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);
}
@@ -3718,7 +3902,7 @@ to_date(PG_FUNCTION_ARGS)
struct pg_tm tm;
fsec_t fsec;
- do_to_timestamp(date_txt, fmt, &tm, &fsec);
+ do_to_timestamp(date_txt, fmt, false, &tm, &fsec, NULL, NULL);
/* Prevent overflow in Julian-day routines */
if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
@@ -3740,10 +3924,175 @@ to_date(PG_FUNCTION_ARGS)
}
/*
+ * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
+ * Actual datatype (returned in 'typid', 'typmod') is determined by
+ * presence of date/time/zone components in the format string.
+ */
+Datum
+to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
+ int32 *typmod, int *tz)
+{
+ struct pg_tm tm;
+ fsec_t fsec;
+ int fprec = 0;
+ int flags;
+
+ do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
+
+ *typmod = fprec ? fprec : -1; /* fractional part precision */
+ *tz = 0;
+
+ if (flags & DCH_DATED)
+ {
+ if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimestampTz result;
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timestamptz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timestamp(&tm, fsec, tz, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamptz out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPTZOID;
+ return TimestampTzGetDatum(result);
+ }
+ else
+ {
+ Timestamp result;
+
+ if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ AdjustTimestampForTypmod(&result, *typmod);
+
+ *typid = TIMESTAMPOID;
+ return TimestampGetDatum(result);
+ }
+ }
+ else
+ {
+ if (flags & DCH_ZONED)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is zoned but not timed")));
+ }
+ else
+ {
+ DateADT result;
+
+ /* Prevent overflow in Julian-day routines */
+ if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
+ POSTGRES_EPOCH_JDATE;
+
+ /* Now check for just-out-of-range dates */
+ if (!IS_VALID_DATE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("date out of range: \"%s\"",
+ text_to_cstring(date_txt))));
+
+ *typid = DATEOID;
+ return DateADTGetDatum(result);
+ }
+ }
+ }
+ else if (flags & DCH_TIMED)
+ {
+ if (flags & DCH_ZONED)
+ {
+ TimeTzADT *result = palloc(sizeof(TimeTzADT));
+
+ if (tm.tm_zone)
+ tzname = (char *) tm.tm_zone;
+
+ if (tzname)
+ {
+ int dterr = DecodeTimezone(tzname, tz);
+
+ if (dterr)
+ DateTimeParseError(dterr, tzname, "timetz");
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("missing time-zone in timestamptz input string")));
+
+ *tz = DetermineTimeZoneOffset(&tm, session_timezone);
+ }
+
+ if (tm2timetz(&tm, fsec, *tz, result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timetz out of range")));
+
+ AdjustTimeForTypmod(&result->time, *typmod);
+
+ *typid = TIMETZOID;
+ return TimeTzADTPGetDatum(result);
+ }
+ else
+ {
+ TimeADT result;
+
+ if (tm2time(&tm, fsec, &result) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
+ AdjustTimeForTypmod(&result, *typmod);
+
+ *typid = TIMEOID;
+ return TimeADTGetDatum(result);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
+ errmsg("datetime format is not dated and not timed")));
+ }
+
+ return (Datum) 0;
+}
+
+/*
* do_to_timestamp: shared code for to_timestamp and to_date
*
* Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm
- * and fractional seconds.
+ * and fractional seconds and fractional precision.
*
* We parse 'fmt' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
@@ -3751,10 +4100,15 @@ to_date(PG_FUNCTION_ARGS)
*
* The TmFromChar is then analysed and converted into the final results in
* struct 'tm' and 'fsec'.
+ *
+ * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags'.
+ *
+ * 'strict' enables error reporting on unmatched trailing characters in input or
+ * format strings patterns.
*/
static void
-do_to_timestamp(text *date_txt, text *fmt,
- struct pg_tm *tm, fsec_t *fsec)
+do_to_timestamp(text *date_txt, text *fmt, bool strict,
+ struct pg_tm *tm, fsec_t *fsec, int *fprec, int *flags)
{
FormatNode *format;
TmFromChar tmfc;
@@ -3807,9 +4161,13 @@ do_to_timestamp(text *date_txt, text *fmt,
/* dump_index(DCH_keywords, DCH_index); */
#endif
- DCH_from_char(format, date_str, &tmfc);
+ DCH_from_char(format, date_str, &tmfc, strict);
pfree(fmt_str);
+
+ if (flags)
+ *flags = DCH_datetime_type(format);
+
if (!incache)
pfree(format);
}
@@ -3991,6 +4349,8 @@ do_to_timestamp(text *date_txt, text *fmt,
*fsec += tmfc.ms * 1000;
if (tmfc.us)
*fsec += tmfc.us;
+ if (fprec)
+ *fprec = tmfc.ff; /* fractional precision, if specified */
/* Range-check date fields according to bit mask computed above */
if (fmask != 0)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7befb6a..5103cd4 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 bec129a..bd15bfa 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 f5ec9bb..8a6f2cd 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 5b275dc..227779c 100644
--- a/src/include/utils/formatting.h
+++ b/src/include/utils/formatting.h
@@ -28,4 +28,7 @@ extern char *asc_tolower(const char *buff, size_t nbytes);
extern char *asc_toupper(const char *buff, size_t nbytes);
extern char *asc_initcap(const char *buff, size_t nbytes);
+extern Datum to_datetime(text *date_txt, text *fmt_txt, char *tzname,
+ bool strict, Oid *typid, int32 *typmod, int *tz);
+
#endif
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index b2b4577..74ecb7c 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -2786,6 +2786,85 @@ SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
Sun Dec 18 03:18:00 2011 PST
(1 row)
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------
+ 1 | Fri Nov 02 12:34:56 2018 PDT
+ 2 | Fri Nov 02 12:34:56 2018 PDT
+ 3 | Fri Nov 02 12:34:56 2018 PDT
+ 4 | Fri Nov 02 12:34:56 2018 PDT
+ 5 | Fri Nov 02 12:34:56 2018 PDT
+ 6 | Fri Nov 02 12:34:56 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+--------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.1 2018 PDT
+ 3 | Fri Nov 02 12:34:56.1 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+---------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.12 2018 PDT
+ 4 | Fri Nov 02 12:34:56.12 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.123 2018 PDT
+ 5 | Fri Nov 02 12:34:56.123 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-----------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 5 | Fri Nov 02 12:34:56.1234 2018 PDT
+ 6 | Fri Nov 02 12:34:56.1234 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12345 2018 PDT
+ 6 | Fri Nov 02 12:34:56.12345 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ i | to_timestamp
+---+-------------------------------------
+ 1 | Fri Nov 02 12:34:56.1 2018 PDT
+ 2 | Fri Nov 02 12:34:56.12 2018 PDT
+ 3 | Fri Nov 02 12:34:56.123 2018 PDT
+ 4 | Fri Nov 02 12:34:56.1235 2018 PDT
+ 5 | Fri Nov 02 12:34:56.12346 2018 PDT
+ 6 | Fri Nov 02 12:34:56.123456 2018 PDT
+(6 rows)
+
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+ERROR: date/time field value out of range: "2018-11-02 12:34:56.123456789"
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 4a2fabd..cb3dd19 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1597,6 +1597,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(65 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
make_timestamp
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 8a4c719..cdd3c14 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1717,6 +1717,21 @@ SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
| 2001 1 1 1 1 1 1
(66 rows)
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+ to_char_12 | to_char
+------------+--------------------------------------------------------------------
+ | 0 00 000 0000 00000 000000 0 00 000 0000 00000 000000 000 000000
+ | 7 78 780 7800 78000 780000 7 78 780 7800 78000 780000 780 780000
+ | 7 78 789 7890 78901 789010 7 78 789 7890 78901 789010 789 789010
+ | 7 78 789 7890 78901 789012 7 78 789 7890 78901 789012 789 789012
+(4 rows)
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index e356dd5..3c85803 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -402,6 +402,15 @@ SELECT to_timestamp('2011-12-18 11:38 +05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 -05:20', 'YYYY-MM-DD HH12:MI TZH:TZM');
SELECT to_timestamp('2011-12-18 11:38 20', 'YYYY-MM-DD HH12:MI TZM');
+SELECT i, to_timestamp('2018-11-02 12:34:56', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.1234', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.12345', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+SELECT i, to_timestamp('2018-11-02 12:34:56.123456789', 'YYYY-MM-DD HH24:MI:SS.FF' || i) FROM generate_series(1, 6) i;
+
--
-- Check handling of multiple spaces in format and/or input
--
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b7957cb..fea4255 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -228,5 +228,13 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMP_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamp),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- timestamp numeric fields constructor
SELECT make_timestamp(2014,12,28,6,30,45.887);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index c3bd46c..588c3e0 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -252,6 +252,14 @@ SELECT '' AS to_char_10, to_char(d1, 'IYYY IYY IY I IW IDDD ID')
SELECT '' AS to_char_11, to_char(d1, 'FMIYYY FMIYY FMIY FMI FMIW FMIDDD FMID')
FROM TIMESTAMPTZ_TBL;
+SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff5 ff6 MS US')
+ FROM (VALUES
+ ('2018-11-02 12:34:56'::timestamptz),
+ ('2018-11-02 12:34:56.78'),
+ ('2018-11-02 12:34:56.78901'),
+ ('2018-11-02 12:34:56.78901234')
+ ) d(d);
+
-- Check OF, TZH, TZM with various zone offsets, particularly fractional hours
SET timezone = '00:00';
SELECT to_char(now(), 'OF') as "OF", to_char(now(), 'TZH:TZM') as "TZH:TZM";
--
2.7.4
0002-Jsonpath-engine-and-docs-v28.patch (text/x-patch, 279.5K) download
From 71889ee367e6a85ab97d0f14cbce34705aceb2b2 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Wed, 23 Jan 2019 13:22:27 +0300
Subject: [PATCH 2/2] Implementation of JSON path language
SQL 2016 standards among other things contains set of SQL/JSON features for
JSON processing inside of relational database. The core of SQL/JSON is JSON
path language, allowing access parts of JSON documents and make computations
over them. This commit implements JSON path language as separate datatype
called "jsonpath".
Support of SQL/JSON features requires implementation of separate nodes, and it
will be considered in subsequent patches. This commit includes following
set of plain functions, allowing to execute jsonpath over jsonb values:
* jsonb_path_exists(jsonb, jsonpath[, jsonb]),
* jsonb_path_match(jsonb, jsonpath[, jsonb]),
* jsonb_path_query(jsonb, jsonpath[, jsonb]),
* jsonb_path_query_array(jsonb, jsonpath[, jsonb]).
* jsonb_path_query_first(jsonb, jsonpath[, jsonb]).
This commit also implements "jsonb @? jsonpath" and "jsonb @@ jsonpath", which
are wrappers over jsonpath_exists(jsonb, jsonpath) and jsonpath_predicate(jsonb,
jsonpath) correspondingly. These operators will have an index support
(implemented in subsequent patches).
Code was written by Nikita Glukhov and Teodor Sigaev, revised by me.
Documentation was written by Oleg Bartunov and Liudmila Mantrova. The work
was inspired by Oleg Bartunov.
Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Teodor Sigaev, Alexander Korotkov, Oleg Bartunov, Liudmila Mantrova
Reviewed-by: Andrew Dunstan, Pavel Stehule, Tomas Vondra, Alexander Korotkov
---
doc/src/sgml/biblio.sgml | 11 +
doc/src/sgml/func.sgml | 760 ++++++-
doc/src/sgml/json.sgml | 250 ++-
src/backend/Makefile | 11 +-
src/backend/utils/adt/.gitignore | 3 +
src/backend/utils/adt/Makefile | 18 +-
src/backend/utils/adt/json.c | 32 +-
src/backend/utils/adt/jsonb.c | 8 +-
src/backend/utils/adt/jsonb_util.c | 21 +
src/backend/utils/adt/jsonpath.c | 898 ++++++++
src/backend/utils/adt/jsonpath_exec.c | 2855 ++++++++++++++++++++++++++
src/backend/utils/adt/jsonpath_gram.y | 493 +++++
src/backend/utils/adt/jsonpath_scan.l | 630 ++++++
src/backend/utils/adt/regexp.c | 4 +-
src/backend/utils/errcodes.txt | 16 +
src/include/catalog/pg_operator.dat | 8 +
src/include/catalog/pg_proc.dat | 41 +
src/include/catalog/pg_type.dat | 5 +
src/include/regex/regex.h | 8 +
src/include/utils/.gitignore | 1 +
src/include/utils/jsonapi.h | 3 +-
src/include/utils/jsonb.h | 27 +-
src/include/utils/jsonpath.h | 300 +++
src/include/utils/jsonpath_scanner.h | 32 +
src/test/regress/expected/jsonb_jsonpath.out | 1765 ++++++++++++++++
src/test/regress/expected/jsonpath.out | 788 +++++++
src/test/regress/parallel_schedule | 7 +-
src/test/regress/serial_schedule | 2 +
src/test/regress/sql/jsonb_jsonpath.sql | 395 ++++
src/test/regress/sql/jsonpath.sql | 144 ++
src/tools/msvc/Mkvcbuild.pm | 2 +
src/tools/msvc/Solution.pm | 18 +
src/tools/pgindent/typedefs.list | 17 +
33 files changed, 9544 insertions(+), 29 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/doc/src/sgml/biblio.sgml b/doc/src/sgml/biblio.sgml
index 4953024..f06305d 100644
--- a/doc/src/sgml/biblio.sgml
+++ b/doc/src/sgml/biblio.sgml
@@ -136,6 +136,17 @@
<pubdate>1988</pubdate>
</biblioentry>
+ <biblioentry id="sqltr-19075-6">
+ <title>SQL Technical Report</title>
+ <subtitle>Part 6: SQL support for JavaScript Object
+ Notation (JSON)</subtitle>
+ <edition>First Edition.</edition>
+ <biblioid>
+ <ulink url="http://standards.iso.org/ittf/PubliclyAvailableStandards/c067367_ISO_IEC_TR_19075-6_2017.zip"></ulink>.
+ </biblioid>
+ <pubdate>2017.</pubdate>
+ </biblioentry>
+
</bibliodiv>
<bibliodiv>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6f5baef..d7a4445 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11313,26 +11313,584 @@ table2-mapping
</sect1>
<sect1 id="functions-json">
- <title>JSON Functions and Operators</title>
+ <title>JSON Functions, Operators, and Expressions</title>
- <indexterm zone="functions-json">
+ <para>
+ The functions, operators, and expressions described in this section
+ operate on JSON data:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ SQL/JSON path expressions
+ (see <xref linkend="functions-sqljson-path"/>).
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ PostgreSQL-specific functions and operators for JSON
+ data types (see <xref linkend="functions-pgjson"/>).
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ To learn more about the SQL/JSON standard, see
+ <xref linkend="sqltr-19075-6"/>. For details on JSON types
+ supported in <productname>PostgreSQL</productname>,
+ see <xref linkend="datatype-json"/>.
+ </para>
+
+ <sect2 id="functions-sqljson-path">
+ <title>SQL/JSON Path Expressions</title>
+
+ <para>
+ SQL/JSON path expressions specify the items to be retrieved
+ from the JSON data, similar to XPath expressions used
+ for SQL access to XML. In <productname>PostgreSQL</productname>,
+ path expressions are implemented as the <type>jsonpath</type>
+ data type, described in <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>JSON query functions and operators
+ pass the provided path expression to the <firstterm>path engine</firstterm>
+ for evaluation. If the expression matches the JSON data to be queried,
+ the corresponding SQL/JSON item is returned.
+ Path expressions are written in the SQL/JSON path language
+ and can also include arithmetic expressions and functions.
+ Query functions treat the provided expression as a
+ text string, so it must be enclosed in single quotes.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of elements allowed
+ by the <type>jsonpath</type> data type.
+ The path expression is evaluated from left to right, but
+ you can use parentheses to change the order of operations.
+ If the evaluation is successful, an SQL/JSON sequence is produced,
+ and the evaluation result is returned to the JSON query function
+ that completes the specified computation.
+ </para>
+
+ <para>
+ To refer to the JSON data to be queried (the
+ <firstterm>context item</firstterm>), use the <literal>$</literal> sign
+ in the path expression. It can be followed by one or more
+ <link linkend="type-jsonpath-accessors">accessor operators</link>,
+ which go down the JSON structure level by level to retrieve the
+ content of context item. Each operator that follows deals with the
+ result of the previous evaluation step.
+ </para>
+
+ <para>
+ For example, suppose you have some JSON data from a GPS tracker that you
+ would like to parse, such as:
+<programlisting>
+{ "track" :
+ {
+ "segments" : [
+ { "location": [ 47.763, 13.4034 ],
+ "start time": "2018-10-14 10:05:14",
+ "HR": 73
+ },
+ { "location": [ 47.706, 13.2635 ],
+ "start time": "2018-10-14 10:39:21",
+ "HR": 130
+ } ]
+ }
+}
+</programlisting>
+ </para>
+
+ <para>
+ To retrieve the available track segments, you need to use the
+ <literal>.<replaceable>key</replaceable></literal> accessor
+ operator for all the preceding JSON objects:
+<programlisting>
+'$.track.segments'
+</programlisting>
+ </para>
+
+ <para>
+ If the item to retrieve is an element of an array, you have
+ to unnest this array using the [*] operator. For example,
+ the following path will return location coordinates for all
+ the available track segments:
+<programlisting>
+'$.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ To return the coordinates of the first segment only, you can
+ specify the corresponding subscript in the <literal>[]</literal>
+ accessor operator. Note that the SQL/JSON arrays are 0-relative:
+<programlisting>
+'$.track.segments[0].location'
+</programlisting>
+ </para>
+
+ <para>
+ The result of each path evaluation step can be processed
+ by one or more <type>jsonpath</type> operators and methods
+ listed in <xref linkend="functions-sqljson-path-operators"/>.
+ Each method must be preceded by a dot, while arithmetic and boolean
+ operators are separated from the operands by spaces. For example,
+ you can convert a text string into a datetime value:
+<programlisting>
+'$.track.segments[*]."start time".datetime()'
+</programlisting>
+ For more examples of using <type>jsonpath</type> operators
+ and methods within path expressions, see
+ <xref linkend="functions-sqljson-path-operators"/>.
+ </para>
+
+ <para>
+ When defining the path, you can also use one or more
+ <firstterm>filter expressions</firstterm>, which work similar to
+ the <command>WHERE</command> clause in SQL. Each filter expression
+ can provide one or more filtering conditions that are applied
+ to the result of the path evaluation. Each filter expression must
+ be enclosed in parentheses and preceded by a question mark.
+ Filter expressions are applied from left to right and can be nested.
+ The <literal>@</literal> variable denotes the current path evaluation
+ result to be filtered, and can be followed by one or more accessor
+ operators to define the JSON element by which to filter the result.
+ Functions and operators that can be used in the filtering condition
+ are listed in <xref linkend="functions-sqljson-filter-ex-table"/>.
+ The result of the filter expression may be true, false, or unknown.
+ </para>
+
+ <para>
+ For example, the following path expression returns the heart
+ rate value only if it is higher than 130:
+<programlisting>
+'$.track.segments[*].HR ? (@ > 130)'
+</programlisting>
+ </para>
+
+ <para>
+ But suppose you would like to retrieve the start time of this segment
+ instead. In this case, you have to filter out irrelevant
+ segments before getting the start time, so the path in the
+ filter condition looks differently:
+<programlisting>
+'$.track.segments[*] ? (@.HR > 130)."start time"'
+</programlisting>
+ </para>
+
+ <para>
+ <productname>PostgreSQL</productname> also implements the following
+ extensions of the SQL/JSON standard:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ A path expression can be a boolean predicate. For example:
+<programlisting>
+'$.track.segments[*].HR < 70'
+</programlisting>
+ </para>
+ </listitem>
+ <listitem>
+ <para>Writing the path as an expression is also valid:
+<programlisting>
+'$' || '.' || 'a'
+</programlisting>
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <sect3 id="strict-and-lax-modes">
+ <title>Strict and Lax Modes</title>
+ <para>
+ When you query JSON data, the path expression may not match the
+ actual JSON data structure. An attempt to access a non-existent
+ member of an object or element of an array results in a
+ structural error. SQL/JSON path expressions have two modes
+ of handling structural errors:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ lax (default) — the path engine implicitly adapts
+ the queried data to the specified path.
+ Any remaining structural errors are suppressed and converted
+ to empty SQL/JSON sequences.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ strict — if a structural error occurs, an error is raised.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The lax mode facilitates matching of a JSON document structure and path
+ expression if the JSON data does not conform to the expected schema.
+ If an operand does not match the requirements of a particular operation,
+ it can be automatically wrapped as an SQL/JSON array or unwrapped by
+ converting its elements into an SQL/JSON sequence before performing
+ this operation. Besides, comparison operators automatically unwrap their
+ operands in the lax mode, so you can compare SQL/JSON arrays
+ out-of-the-box. Arrays of size 1 are interchangeable with a singleton.
+ </para>
+
+ <para>
+ For example, when querying the GPS data listed above, you can
+ abstract from the fact that it stores an array of segments
+ when using the lax mode:
+<programlisting>
+'lax $.track.segments.location'
+</programlisting>
+ </para>
+
+ <para>
+ In the strict mode, the specified path must exactly match the structure of
+ the queried JSON document to return an SQL/JSON item, so using this
+ path expression will cause an error. To get the same result as in
+ the lax mode, you have to explicitly unwrap the
+ <literal>segments</literal> array:
+<programlisting>
+'strict $.track.segments[*].location'
+</programlisting>
+ </para>
+
+ <para>
+ Implicit unwrapping in the lax mode is not performed in the following cases:
+ <itemizedlist>
+ <listitem>
+ <para>
+ The path expression contains <literal>type()</literal> or
+ <literal>size()</literal> methods that return the type
+ and the number of elements in the array, respectively.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ The queried JSON data contain nested arrays. In this case, only
+ the outermost array is unwrapped, while all the inner arrays
+ remain unchanged. Thus, implicit unwrapping can only go one
+ level down within each path evaluation step.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect3>
+
+ <sect3 id="functions-sqljson-path-operators">
+ <title>SQL/JSON Path Operators and Methods</title>
+
+ <table id="functions-sqljson-op-table">
+ <title><type>jsonpath</type> Operators and Methods</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Operator/Method</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>+</literal> (unary)</entry>
+ <entry>Plus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>+ $.x.floor()</literal></entry>
+ <entry><literal>2, -15, -10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (unary)</entry>
+ <entry>Minus operator that iterates over the json sequence</entry>
+ <entry><literal>{"x": [2.85, -14.7, -9.4]}</literal></entry>
+ <entry><literal>- $.x.floor()</literal></entry>
+ <entry><literal>-2, 15, 10</literal></entry>
+ </row>
+ <row>
+ <entry><literal>+</literal> (binary)</entry>
+ <entry>Addition</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>2 + $[0]</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>-</literal> (binary)</entry>
+ <entry>Subtraction</entry>
+ <entry><literal>[2]</literal></entry>
+ <entry><literal>4 - $[0]</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>*</literal></entry>
+ <entry>Multiplication</entry>
+ <entry><literal>[4]</literal></entry>
+ <entry><literal>2 * $[0]</literal></entry>
+ <entry><literal>8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>/</literal></entry>
+ <entry>Division</entry>
+ <entry><literal>[8]</literal></entry>
+ <entry><literal>$[0] / 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>%</literal></entry>
+ <entry>Modulus</entry>
+ <entry><literal>[32]</literal></entry>
+ <entry><literal>$[0] % 10</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>type()</literal></entry>
+ <entry>Type of the SQL/JSON item</entry>
+ <entry><literal>[1, "2", {}]</literal></entry>
+ <entry><literal>$[*].type()</literal></entry>
+ <entry><literal>"number", "string", "object"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>size()</literal></entry>
+ <entry>Size of the SQL/JSON item</entry>
+ <entry><literal>{"m": [11, 15]}</literal></entry>
+ <entry><literal>$.m.size()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>double()</literal></entry>
+ <entry>Approximate numeric value converted from a string</entry>
+ <entry><literal>{"len": "1.9"}</literal></entry>
+ <entry><literal>$.len.double() * 2</literal></entry>
+ <entry><literal>3.8</literal></entry>
+ </row>
+ <row>
+ <entry><literal>ceiling()</literal></entry>
+ <entry>Nearest integer greater than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.ceiling()</literal></entry>
+ <entry><literal>2</literal></entry>
+ </row>
+ <row>
+ <entry><literal>floor()</literal></entry>
+ <entry>Nearest integer less than or equal to the SQL/JSON number</entry>
+ <entry><literal>{"h": 1.3}</literal></entry>
+ <entry><literal>$.h.floor()</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>abs()</literal></entry>
+ <entry>Absolute value of the SQL/JSON number</entry>
+ <entry><literal>{"z": -0.3}</literal></entry>
+ <entry><literal>$.z.abs()</literal></entry>
+ <entry><literal>0.3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime()</literal></entry>
+ <entry>Datetime value converted from a string</entry>
+ <entry><literal>["2015-8-1", "2015-08-12"]</literal></entry>
+ <entry><literal>$[*] ? (@.datetime() < "2015-08-2". datetime())</literal></entry>
+ <entry><literal>2015-8-1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template</entry>
+ <entry><literal>["12:30", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI")</literal></entry>
+ <entry><literal>"12:30:00", "18:40:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>datetime(<replaceable>template</replaceable>, <replaceable>default_tz</replaceable>)</literal></entry>
+ <entry>Datetime value converted from a string with a specified template and default timezone</entry>
+ <entry><literal>["12:30 -02", "18:40"]</literal></entry>
+ <entry><literal>$[*].datetime("HH24:MI TZH", "+03:00")</literal></entry>
+ <entry><literal>"12:30:00-02:00", "18:40:00+03:00"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>keyvalue()</literal></entry>
+ <entry>
+ Sequence of object's key-value pairs represented as array of objects
+ containing three fields (<literal>"key"</literal>,
+ <literal>"value"</literal>, and <literal>"id"</literal>).
+ <literal>"id"</literal> is an unique identifier of the object
+ key-value pair belongs to.
+ </entry>
+ <entry><literal>{"x": "20", "y": 32}</literal></entry>
+ <entry><literal>$.keyvalue()</literal></entry>
+ <entry><literal>{"key": "x", "value": "20", "id": 0}, {"key": "y", "value": 32, "id": 0}</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="functions-sqljson-filter-ex-table">
+ <title><type>jsonpath</type> Filter Expression Elements</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Value/Predicate</entry>
+ <entry>Description</entry>
+ <entry>Example JSON</entry>
+ <entry>Example Query</entry>
+ <entry>Result</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>==</literal></entry>
+ <entry>Equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ == 1)</literal></entry>
+ <entry><literal>1, 1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!=</literal></entry>
+ <entry>Non-equality operator</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ != 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><></literal></entry>
+ <entry>Non-equality operator (same as <literal>!=</literal>)</entry>
+ <entry><literal>[1, 2, 1, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ <> 1)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal><</literal></entry>
+ <entry>Less-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1, 2</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>Less-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ < 2)</literal></entry>
+ <entry><literal>1</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ > 2)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>Greater-than-or-equal-to operator</entry>
+ <entry><literal>[1, 2, 3]</literal></entry>
+ <entry><literal>$[*] ? (@ >= 2)</literal></entry>
+ <entry><literal>2, 3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>true</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>true</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == true)</literal></entry>
+ <entry><literal>{"name": "Chris", "parent": true}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>false</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>false</literal> literal</entry>
+ <entry><literal>[{"name": "John", "parent": false},
+ {"name": "Chris", "parent": true}]</literal></entry>
+ <entry><literal>$[*] ? (@.parent == false)</literal></entry>
+ <entry><literal>{"name": "John", "parent": false}</literal></entry>
+ </row>
+ <row>
+ <entry><literal>null</literal></entry>
+ <entry>Value used to perform comparison with JSON <literal>null</literal> value</entry>
+ <entry><literal>[{"name": "Mary", "job": null},
+ {"name": "Michael", "job": "driver"}]</literal></entry>
+ <entry><literal>$[*] ? (@.job == null) .name</literal></entry>
+ <entry><literal>"Mary"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>&&</literal></entry>
+ <entry>Boolean AND</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ > 1 && @ < 5)</literal></entry>
+ <entry><literal>3</literal></entry>
+ </row>
+ <row>
+ <entry><literal>||</literal></entry>
+ <entry>Boolean OR</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (@ < 1 || @ > 5)</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>!</literal></entry>
+ <entry>Boolean NOT</entry>
+ <entry><literal>[1, 3, 7]</literal></entry>
+ <entry><literal>$[*] ? (!(@ < 5))</literal></entry>
+ <entry><literal>7</literal></entry>
+ </row>
+ <row>
+ <entry><literal>like_regex</literal></entry>
+ <entry>Tests pattern matching with POSIX regular expressions</entry>
+ <entry><literal>["abc", "abd", "aBdC", "abdacb", "babc"]</literal></entry>
+ <entry><literal>$[*] ? (@ like_regex "^ab.*c" flag "i")</literal></entry>
+ <entry><literal>"abc", "aBdC", "abdacb"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>starts with</literal></entry>
+ <entry>Tests whether the second operand is an initial substring of the first operand</entry>
+ <entry><literal>["John Smith", "Mary Stone", "Bob Johnson"]</literal></entry>
+ <entry><literal>$[*] ? (@ starts with "John")</literal></entry>
+ <entry><literal>"John Smith"</literal></entry>
+ </row>
+ <row>
+ <entry><literal>exists</literal></entry>
+ <entry>Tests whether a path expression has at least one SQL/JSON item</entry>
+ <entry><literal>{"x": [1, 2], "y": [2, 4]}</literal></entry>
+ <entry><literal>strict $.* ? (exists (@ ? (@[*] > 2)))</literal></entry>
+ <entry><literal>2, 4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>is unknown</literal></entry>
+ <entry>Tests whether a boolean condition is <literal>unknown</literal></entry>
+ <entry><literal>[-1, 2, 7, "infinity"]</literal></entry>
+ <entry><literal>$[*] ? ((@ > 0) is unknown)</literal></entry>
+ <entry><literal>"infinity"</literal></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect3>
+
+ </sect2>
+
+ <sect2 id="functions-pgjson">
+ <title>PostgreSQL-specific JSON Functions and Operators</title>
+ <indexterm zone="functions-pgjson">
<primary>JSON</primary>
<secondary>functions and operators</secondary>
</indexterm>
- <para>
+ <para>
<xref linkend="functions-json-op-table"/> shows the operators that
- are available for use with the two JSON data types (see <xref
+ are available for use with JSON data types (see <xref
linkend="datatype-json"/>).
</para>
<table id="functions-json-op-table">
<title><type>json</type> and <type>jsonb</type> Operators</title>
- <tgroup cols="5">
+ <tgroup cols="6">
<thead>
<row>
<entry>Operator</entry>
<entry>Right Operand Type</entry>
+ <entry>Return type</entry>
<entry>Description</entry>
<entry>Example</entry>
<entry>Example Result</entry>
@@ -11342,6 +11900,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON array element (indexed from zero, negative
integers count from the end)</entry>
<entry><literal>'[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2</literal></entry>
@@ -11350,6 +11909,7 @@ table2-mapping
<row>
<entry><literal>-></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
<entry>Get JSON object field by key</entry>
<entry><literal>'{"a": {"b":"foo"}}'::json->'a'</literal></entry>
<entry><literal>{"b":"foo"}</literal></entry>
@@ -11357,6 +11917,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>int</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON array element as <type>text</type></entry>
<entry><literal>'[1,2,3]'::json->>2</literal></entry>
<entry><literal>3</literal></entry>
@@ -11364,6 +11925,7 @@ table2-mapping
<row>
<entry><literal>->></literal></entry>
<entry><type>text</type></entry>
+ <entry><type>text</type></entry>
<entry>Get JSON object field as <type>text</type></entry>
<entry><literal>'{"a":1,"b":2}'::json->>'b'</literal></entry>
<entry><literal>2</literal></entry>
@@ -11371,14 +11933,16 @@ table2-mapping
<row>
<entry><literal>#></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path</entry>
+ <entry><type>json</type> or <type>jsonb</type></entry>
+ <entry>Get JSON object at the specified path</entry>
<entry><literal>'{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}'</literal></entry>
<entry><literal>{"c": "foo"}</literal></entry>
</row>
<row>
<entry><literal>#>></literal></entry>
<entry><type>text[]</type></entry>
- <entry>Get JSON object at specified path as <type>text</type></entry>
+ <entry><type>text</type></entry>
+ <entry>Get JSON object at the specified path as <type>text</type></entry>
<entry><literal>'{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}'</literal></entry>
<entry><literal>3</literal></entry>
</row>
@@ -11503,6 +12067,18 @@ table2-mapping
JSON arrays, negative integers count from the end)</entry>
<entry><literal>'["a", {"b":1}]'::jsonb #- '{1,b}'</literal></entry>
</row>
+ <row>
+ <entry><literal>@?</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>Does JSON path returns any item for the specified JSON value?</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @? '$.a[*] ? (@ > 2)'</literal></entry>
+ </row>
+ <row>
+ <entry><literal>@@</literal></entry>
+ <entry><type>jsonpath</type></entry>
+ <entry>JSON path predicate check result for the specified JSON value</entry>
+ <entry><literal>'{"a":[1,2,3,4,5]}'::jsonb @@ '$.a[*] > 2'</literal></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -11776,6 +12352,21 @@ table2-mapping
<indexterm>
<primary>jsonb_pretty</primary>
</indexterm>
+ <indexterm>
+ <primary>jsonb_path_exists</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_match</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_array</primary>
+ </indexterm>
+ <indexterm>
+ <primary>jsonb_path_query_first</primary>
+ </indexterm>
<table id="functions-json-processing-table">
<title>JSON Processing Functions</title>
@@ -12110,6 +12701,159 @@ table2-mapping
</programlisting>
</entry>
</row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Checks whether JSON path returns any item for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_match(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>boolean</type></entry>
+ <entry>
+ Returns JSON path predicate result for the specified JSON value.
+ Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', '$.a[*] > 2')
+ </literal></para>
+ <para><literal>
+ jsonb_path_match('{"a":[1,2,3,4,5]}', 'exists($.a[*] ? (@ >= $min && @ <= $max))', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>true</literal></para>
+ <para><literal>true</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>setof jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value. Variables are substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)');
+ </literal></para>
+ <para><literal>
+ select * jsonb_path_query('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}');
+ </literal></para>
+ </entry>
+ <entry>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 3
+ 4
+ 5
+ </programlisting>
+ </para>
+ <para>
+ <programlisting>
+ jsonb_path_query
+------------------
+ 2
+ 3
+ 4
+ </programlisting>
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets all JSON items returned by JSON path for the specified JSON
+ value and wraps result into an array. Variables are substituted to
+ JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_array('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>[3, 4, 5]</literal></para>
+ <para><literal>[2, 3, 4]</literal></para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath)
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first(target jsonb, path jsonpath, variables jsonb)
+ </literal></para>
+ </entry>
+ <entry><type>jsonb</type></entry>
+ <entry>
+ Gets the first JSON item returned by JSON path for the specified JSON
+ value. Returns <literal>NULL</literal> on no results. Variables are
+ substituted to JSON path if specified.
+ </entry>
+ <entry>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ > 2)')
+ </literal></para>
+ <para><literal>
+ jsonb_path_query_first('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '{"min":2,"max":4}')
+ </literal></para>
+ </entry>
+ <entry>
+ <para><literal>3</literal></para>
+ <para><literal>2</literal></para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12147,6 +12891,7 @@ table2-mapping
JSON fields that do not appear in the target row type will be
omitted from the output, and target columns that do not match any
JSON field will simply be NULL.
+
</para>
</note>
@@ -12201,6 +12946,7 @@ table2-mapping
<function>jsonb_agg</function> and <function>jsonb_object_agg</function>.
</para>
+ </sect2>
</sect1>
<sect1 id="functions-sequence">
diff --git a/doc/src/sgml/json.sgml b/doc/src/sgml/json.sgml
index e7b68fa..12a7073 100644
--- a/doc/src/sgml/json.sgml
+++ b/doc/src/sgml/json.sgml
@@ -22,8 +22,16 @@
</para>
<para>
- There are two JSON data types: <type>json</type> and <type>jsonb</type>.
- They accept <emphasis>almost</emphasis> identical sets of values as
+ <productname>PostgreSQL</productname> offers two types for storing JSON
+ data: <type>json</type> and <type>jsonb</type>. To implement effective query
+ mechanisms for these data types, <productname>PostgreSQL</productname>
+ also provides the <type>jsonpath</type> data type described in
+ <xref linkend="datatype-jsonpath"/>.
+ </para>
+
+ <para>
+ The <type>json</type> and <type>jsonb</type> data types
+ accept <emphasis>almost</emphasis> identical sets of values as
input. The major practical difference is one of efficiency. The
<type>json</type> data type stores an exact copy of the input text,
which processing functions must reparse on each execution; while
@@ -217,6 +225,11 @@ SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
in this example, even though those are semantically insignificant for
purposes such as equality checks.
</para>
+
+ <para>
+ For the list of built-in functions and operators available for
+ constructing and processing JSON values, see <xref linkend="functions-json"/>.
+ </para>
</sect2>
<sect2 id="json-doc-design">
@@ -536,6 +549,19 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
</para>
<para>
+ <literal>jsonb_ops</literal> and <literal>jsonb_path_ops</literal> also
+ support queries with <type>jsonpath</type> operators <literal>@?</literal>
+ and <literal>@@</literal>. The previous example for <literal>@></literal>
+ operator can be rewritten as follows:
+ <programlisting>
+-- Find documents in which the key "tags" contains array element "qui"
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
+</programlisting>
+
+ </para>
+
+ <para>
<type>jsonb</type> also supports <literal>btree</literal> and <literal>hash</literal>
indexes. These are usually useful only if it's important to check
equality of complete JSON documents.
@@ -593,4 +619,224 @@ SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qu
lists, and scalars, as appropriate.
</para>
</sect2>
+
+ <sect2 id="datatype-jsonpath">
+ <title>jsonpath Type</title>
+
+ <indexterm zone="datatype-jsonpath">
+ <primary>jsonpath</primary>
+ </indexterm>
+
+ <para>
+ The <type>jsonpath</type> type implements support for the SQL/JSON path language
+ in <productname>PostgreSQL</productname> to effectively query JSON data.
+ It provides a binary representation of the parsed SQL/JSON path
+ expression that specifies the items to be retrieved by the path
+ engine from the JSON data for further processing with the
+ SQL/JSON query functions.
+ </para>
+
+ <para>
+ The SQL/JSON path language is fully integrated into the SQL engine:
+ the semantics of its predicates and operators generally follow SQL.
+ At the same time, to provide a most natural way of working with JSON data,
+ SQL/JSON path syntax uses some of the JavaScript conventions:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Dot <literal>.</literal> is used for member access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Square brackets <literal>[]</literal> are used for array access.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ SQL/JSON arrays are 0-relative, unlike regular SQL arrays that start from 1.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ An SQL/JSON path expression is an SQL character string literal,
+ so it must be enclosed in single quotes when passed to an SQL/JSON
+ query function. Following the JavaScript
+ conventions, character string literals within the path expression
+ must be enclosed in double quotes. Any single quotes within this
+ character string literal must be escaped with a single quote
+ by the SQL convention.
+ </para>
+
+ <para>
+ A path expression consists of a sequence of path elements,
+ which can be the following:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Path literals of JSON primitive types:
+ Unicode text, numeric, true, false, or null.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Path variables listed in <xref linkend="type-jsonpath-variables"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Accessor operators listed in <xref linkend="type-jsonpath-accessors"/>.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <type>jsonpath</type> operators and methods listed
+ in <xref linkend="functions-sqljson-path-operators"/>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Parentheses, which can be used to provide filter expressions
+ or define the order of path evaluation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ For details on using <type>jsonpath</type> expressions with SQL/JSON
+ query functions, see <xref linkend="functions-sqljson-path"/>.
+ </para>
+
+ <table id="type-jsonpath-variables">
+ <title><type>jsonpath</type> Variables</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Variable</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>$</literal></entry>
+ <entry>A variable representing the JSON text to be queried
+ (the <firstterm>context item</firstterm>).
+ </entry>
+ </row>
+ <row>
+ <entry><literal>$varname</literal></entry>
+ <entry>A named variable. Its value must be set in the
+ <command>PASSING</command> clause of an SQL/JSON query function.
+ <!-- TBD: See <xref linkend="sqljson-input-clause"/> -->
+ for details.
+ </entry>
+ </row>
+ <row>
+ <entry><literal>@</literal></entry>
+ <entry>A variable representing the result of path evaluation
+ in filter expressions.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <table id="type-jsonpath-accessors">
+ <title><type>jsonpath</type> Accessors</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Accessor Operator</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>.<replaceable>key</replaceable></literal>
+ </para>
+ <para>
+ <literal>."$<replaceable>varname</replaceable>"</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Member accessor that returns an object member with
+ the specified key. If the key name is a named variable
+ starting with <literal>$</literal> or does not meet the
+ JavaScript rules of an identifier, it must be enclosed in
+ double quotes as a character string literal.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.*</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard member accessor that returns the values of all
+ members located at the top level of the current object.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>.**</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Recursive wildcard member accessor that processes all levels
+ of the JSON hierarchy of the current object and returns all
+ the member values, regardless of their nesting level. This
+ is a <productname>PostgreSQL</productname> extension of
+ the SQL/JSON standard.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[<replaceable>subscript</replaceable>, ...]</literal>
+ </para>
+ <para>
+ <literal>[<replaceable>subscript</replaceable> to last]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Array element accessor. The provided numeric subscripts return the
+ corresponding array elements. The first element in an array is
+ accessed with [0]. The <literal>last</literal> keyword denotes
+ the last subscript in an array and can be used to handle arrays
+ of unknown length.
+ </para>
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <para>
+ <literal>[*]</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Wildcard array element accessor that returns all array elements.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </sect2>
</sect1>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 478a96d..31d9d66 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
@@ -308,6 +316,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/utils/adt/.gitignore b/src/backend/utils/adt/.gitignore
new file mode 100644
index 0000000..7fab054
--- /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 20eead1..d335eec 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/json.c b/src/backend/utils/adt/json.c
index de0d072..5239903 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1506,7 +1506,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, DATEOID);
+ JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1514,7 +1514,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1522,7 +1522,7 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
{
char buf[MAXDATELEN + 1];
- JsonEncodeDateTime(buf, val, TIMESTAMPTZOID);
+ JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
}
break;
@@ -1550,10 +1550,11 @@ datum_to_json(Datum val, bool is_null, StringInfo result,
/*
* Encode 'value' of datetime type 'typid' into JSON string in ISO format using
- * optionally preallocated buffer 'buf'.
+ * optionally preallocated buffer 'buf'. Optional 'tzp' determines time-zone
+ * offset (in seconds) in which we want to show timestamptz.
*/
char *
-JsonEncodeDateTime(char *buf, Datum value, Oid typid)
+JsonEncodeDateTime(char *buf, Datum value, Oid typid, const int *tzp)
{
if (!buf)
buf = palloc(MAXDATELEN + 1);
@@ -1630,11 +1631,30 @@ JsonEncodeDateTime(char *buf, Datum value, Oid typid)
const char *tzn = NULL;
timestamp = DatumGetTimestampTz(value);
+
+ /*
+ * If time-zone is specified, we apply a time-zone shift,
+ * convert timestamptz to pg_tm as if it was without
+ * time-zone, and then use specified time-zone for encoding
+ * timestamp into a string.
+ */
+ if (tzp)
+ {
+ tz = *tzp;
+ timestamp -= (TimestampTz) tz * USECS_PER_SEC;
+ }
+
/* Same as timestamptz_out(), but forcing DateStyle */
if (TIMESTAMP_NOT_FINITE(timestamp))
EncodeSpecialTimestamp(timestamp, buf);
- else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ else if (timestamp2tm(timestamp, tzp ? NULL : &tz, &tm, &fsec,
+ tzp ? NULL : &tzn, NULL) == 0)
+ {
+ if (tzp)
+ tm.tm_isdst = 1; /* set time-zone presence flag */
+
EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
+ }
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c
index c02c856..f27fd0a 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:
@@ -1857,7 +1857,7 @@ jsonb_object_agg_finalfn(PG_FUNCTION_ARGS)
/*
* Extract scalar value from raw-scalar pseudo-array jsonb.
*/
-static bool
+bool
JsonbExtractScalar(JsonbContainer *jbc, JsonbValue *res)
{
JsonbIterator *it;
diff --git a/src/backend/utils/adt/jsonb_util.c b/src/backend/utils/adt/jsonb_util.c
index 6695363..c4bb7c8 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,28 @@ convertJsonbScalar(StringInfo buffer, JEntry *jentry, JsonbValue *scalarVal)
JENTRY_ISBOOL_TRUE : JENTRY_ISBOOL_FALSE;
break;
+ case jbvDatetime:
+ {
+ char buf[MAXDATELEN + 1];
+ size_t len;
+
+ JsonEncodeDateTime(buf,
+ scalarVal->val.datetime.value,
+ scalarVal->val.datetime.typid,
+ &scalarVal->val.datetime.tz);
+ len = strlen(buf);
+ appendToBuffer(buffer, buf, len);
+
+ *jentry = JENTRY_ISSTRING | len;
+ }
+ break;
+
default:
elog(ERROR, "invalid jsonb scalar type");
}
}
+
/*
* Compare two jbvString JsonbValue values, a and b.
*
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
new file mode 100644
index 0000000..62aabf7
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath.c
@@ -0,0 +1,898 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.c
+ * Input/output and supporting routines for jsonpath
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/json.h"
+#include "utils/jsonpath.h"
+
+/*****************************INPUT/OUTPUT************************************/
+
+/*
+ * alignStringInfoInt - aling StringInfo to int by adding
+ * zero padding bytes
+ */
+static 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;
+ }
+}
+
+/*
+ * Convert AST to flat jsonpath type representation
+ */
+static int
+flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+ 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();
+
+ appendStringInfoChar(buf, (char) (item->type));
+ 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)
+ {
+ case jpiString:
+ case jpiVariable:
+ case jpiKey:
+ 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:
+ appendBinaryStringInfo(buf, (char *) item->value.numeric,
+ VARSIZE(item->value.numeric));
+ break;
+ case jpiBool:
+ appendBinaryStringInfo(buf, (char *) &item->value.boolean,
+ sizeof(item->value.boolean));
+ 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:
+ case jpiStartsWith:
+ case jpiDatetime:
+ {
+ 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));
+
+ chld = !item->value.args.left ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.left,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + left) = chld - pos;
+ chld = !item->value.args.right ? pos :
+ flattenJsonPathParseItem(buf, item->value.args.right,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + right) = chld - pos;
+ }
+ 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,
+ nestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + offs) = chld - pos;
+ }
+ break;
+ case jpiFilter:
+ argNestingLevel++;
+ /* fall through */
+ case jpiIsUnknown:
+ case jpiNot:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiExists:
+ {
+ int32 arg;
+
+ arg = buf->len;
+ appendBinaryStringInfo(buf, (char *) &arg /* fake value */ , sizeof(arg));
+
+ chld = flattenJsonPathParseItem(buf, item->value.arg,
+ nestingLevel + argNestingLevel,
+ insideArraySubscript);
+ *(int32 *) (buf->data + arg) = chld - pos;
+ }
+ break;
+ case jpiNull:
+ break;
+ case jpiRoot:
+ break;
+ case jpiAnyArray:
+ case jpiAnyKey:
+ break;
+ case jpiCurrent:
+ if (nestingLevel <= 0)
+ ereport(ERROR,
+ (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;
+ 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,
+ nestingLevel, true) - pos;
+
+ if (item->value.array.elems[i].to)
+ topos = flattenJsonPathParseItem(buf,
+ item->value.array.elems[i].to,
+ nestingLevel, true) - pos;
+ else
+ topos = 0;
+
+ ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
+
+ ppos[0] = frompos;
+ ppos[1] = topos;
+ }
+ }
+ break;
+ case jpiAny:
+ appendBinaryStringInfo(buf,
+ (char *) &item->value.anybounds.first,
+ sizeof(item->value.anybounds.first));
+ appendBinaryStringInfo(buf,
+ (char *) &item->value.anybounds.last,
+ sizeof(item->value.anybounds.last));
+ break;
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath item type: %d", item->type);
+ }
+
+ if (item->next)
+ {
+ chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+ insideArraySubscript) - pos;
+ *(int32 *) (buf->data + next) = chld;
+ }
+
+ return pos;
+}
+
+Datum
+jsonpath_in(PG_FUNCTION_ARGS)
+{
+ char *in = PG_GETARG_CSTRING(0);
+ int32 len = strlen(in);
+ JsonPathParseResult *jsonpath = parsejsonpath(in, len);
+ JsonPath *res;
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ enlargeStringInfo(&buf, 4 * len /* estimation */ );
+
+ appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
+
+ if (!jsonpath)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
+
+ flattenJsonPathParseItem(&buf, jsonpath->expr, 0, 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);
+}
+
+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, " == ", 4);
+ break;
+ case jpiNotEqual:
+ appendBinaryStringInfo(buf, " != ", 4);
+ 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;
+ 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;
+ case jpiStartsWith:
+ appendBinaryStringInfo(buf, " starts with ", 13);
+ 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:
+ case jpiStartsWith:
+ return 2;
+ case jpiAdd:
+ case jpiSub:
+ return 3;
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ return 4;
+ case jpiPlus:
+ case jpiMinus:
+ return 5;
+ default:
+ return 6;
+ }
+}
+
+static void
+printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracketes)
+{
+ JsonPathItem elem;
+ int i;
+
+ 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:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiStartsWith:
+ if (printBracketes)
+ appendStringInfoChar(buf, '(');
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ printOperation(buf, v->type);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false,
+ operationPriority(elem.type) <=
+ operationPriority(v->type));
+ 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)
+ 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);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiNot:
+ appendBinaryStringInfo(buf, "!(", 2);
+ jspGetArg(v, &elem);
+ 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 jpiExists:
+ appendBinaryStringInfo(buf, "exists (", 8);
+ jspGetArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiCurrent:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '@');
+ break;
+ case jpiRoot:
+ Assert(!inKey);
+ appendStringInfoChar(buf, '$');
+ break;
+ case jpiLast:
+ appendBinaryStringInfo(buf, "last", 4);
+ break;
+ case jpiAnyArray:
+ appendBinaryStringInfo(buf, "[*]", 3);
+ break;
+ case jpiAnyKey:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+ appendStringInfoChar(buf, '*');
+ break;
+ case jpiIndexArray:
+ appendStringInfoChar(buf, '[');
+ for (i = 0; i < v->content.array.nelems; i++)
+ {
+ JsonPathItem from;
+ JsonPathItem to;
+ bool range = jspGetArraySubscript(v, &from, &to, i);
+
+ if (i)
+ appendStringInfoChar(buf, ',');
+
+ printJsonPathItem(buf, &from, false, false);
+
+ if (range)
+ {
+ appendBinaryStringInfo(buf, " to ", 4);
+ printJsonPathItem(buf, &to, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ']');
+ break;
+ case jpiAny:
+ if (inKey)
+ appendStringInfoChar(buf, '.');
+
+ if (v->content.anybounds.first == 0 &&
+ v->content.anybounds.last == PG_UINT32_MAX)
+ appendBinaryStringInfo(buf, "**", 2);
+ else if (v->content.anybounds.first == v->content.anybounds.last)
+ {
+ 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 to %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(", 10);
+ if (v->content.args.left)
+ {
+ jspGetLeftArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+
+ if (v->content.args.right)
+ {
+ appendBinaryStringInfo(buf, ", ", 2);
+ jspGetRightArg(v, &elem);
+ printJsonPathItem(buf, &elem, false, false);
+ }
+ }
+ appendStringInfoChar(buf, ')');
+ break;
+ case jpiKeyValue:
+ appendBinaryStringInfo(buf, ".keyvalue()", 11);
+ break;
+ 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 */ );
+
+ if (!(in->header & JSONPATH_LAX))
+ appendBinaryStringInfo(&buf, "strict ", 7);
+
+ jspInit(&v, in);
+ printJsonPathItem(&buf, &v, false, true);
+
+ PG_RETURN_CSTRING(buf.data);
+}
+
+/********************Support functions for JsonPath****************************/
+
+/*
+ * Support macroses to read stored values
+ */
+
+#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_n(v, b, p, n) do { \
+ (v) = (void *)((b) + (p)); \
+ (p) += sizeof(int32) * (n); \
+} while(0) \
+
+/*
+ * Read root node and fill root node representation
+ */
+void
+jspInit(JsonPathItem *v, JsonPath *js)
+{
+ Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+ jspInitByBuffer(v, js->data, 0);
+}
+
+/*
+ * Read node from buffer and fill its representation
+ */
+void
+jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
+{
+ v->base = base + pos;
+
+ read_byte(v->type, base, pos);
+ pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
+ read_int32(v->nextPos, base, pos);
+
+ switch (v->type)
+ {
+ case jpiNull:
+ case jpiRoot:
+ case jpiCurrent:
+ case jpiAnyArray:
+ case jpiAnyKey:
+ case jpiType:
+ case jpiSize:
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiKeyValue:
+ case jpiLast:
+ break;
+ case jpiKey:
+ case jpiString:
+ case jpiVariable:
+ read_int32(v->content.value.datalen, base, pos);
+ /* follow next */
+ case jpiNumeric:
+ case jpiBool:
+ v->content.value.data = base + pos;
+ break;
+ case jpiAnd:
+ case jpiOr:
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiStartsWith:
+ case jpiDatetime:
+ 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:
+ case jpiPlus:
+ case jpiMinus:
+ case jpiFilter:
+ read_int32(v->content.arg, base, 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 * 2);
+ break;
+ case jpiAny:
+ 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);
+ }
+}
+
+void
+jspGetArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(v->type == jpiFilter ||
+ v->type == jpiNot ||
+ v->type == jpiIsUnknown ||
+ v->type == jpiExists ||
+ v->type == jpiPlus ||
+ v->type == jpiMinus);
+
+ jspInitByBuffer(a, v->base, v->content.arg);
+}
+
+bool
+jspGetNext(JsonPathItem *v, JsonPathItem *a)
+{
+ if (jspHasNext(v))
+ {
+ Assert(v->type == jpiString ||
+ v->type == jpiNumeric ||
+ v->type == jpiBool ||
+ v->type == jpiNull ||
+ v->type == jpiKey ||
+ v->type == jpiAny ||
+ v->type == jpiAnyArray ||
+ v->type == jpiAnyKey ||
+ v->type == jpiIndexArray ||
+ v->type == jpiFilter ||
+ v->type == jpiCurrent ||
+ v->type == jpiExists ||
+ 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 == 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 ||
+ v->type == jpiFloor ||
+ v->type == jpiCeiling ||
+ v->type == jpiDouble ||
+ v->type == jpiDatetime ||
+ v->type == jpiKeyValue ||
+ v->type == jpiStartsWith);
+
+ 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 ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith);
+
+ jspInitByBuffer(a, v->base, v->content.args.left);
+}
+
+void
+jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
+{
+ Assert(v->type == jpiAnd ||
+ 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 ||
+ v->type == jpiDatetime ||
+ v->type == jpiStartsWith);
+
+ jspInitByBuffer(a, v->base, v->content.args.right);
+}
+
+bool
+jspGetBool(JsonPathItem *v)
+{
+ Assert(v->type == jpiBool);
+
+ return (bool) *v->content.value.data;
+}
+
+Numeric
+jspGetNumeric(JsonPathItem *v)
+{
+ Assert(v->type == jpiNumeric);
+
+ return (Numeric) v->content.value.data;
+}
+
+char *
+jspGetString(JsonPathItem *v, int32 *len)
+{
+ Assert(v->type == jpiKey ||
+ v->type == jpiString ||
+ v->type == jpiVariable);
+
+ if (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
new file mode 100644
index 0000000..982867f
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -0,0 +1,2855 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_exec.c
+ * Routines for SQL/JSON path execution.
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
+#include "miscadmin.h"
+#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"
+
+/* Special pseudo-ErrorData with zero sqlerrcode for existence queries. */
+ErrorData jperNotFound[1];
+
+typedef struct JsonBaseObjectInfo
+{
+ JsonbContainer *jbc;
+ 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 */
+ 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;
+} JsonValueListIterator;
+
+#define JsonValueListIteratorEnd ((ListCell *) -1)
+
+static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found);
+
+static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+
+
+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;
+}
+
+/*
+ * Get the next item from the sequence advancing iterator.
+ */
+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);
+}
+
+/*
+ * Initialize a binary JsonbValue with the given jsonb container.
+ */
+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;
+}
+
+/*
+ * Transform a JsonbValue into a binary JsonbValue by encoding it to a
+ * binary jsonb container.
+ */
+static inline JsonbValue *
+JsonbWrapInBinary(JsonbValue *jbv, JsonbValue *out)
+{
+ Jsonb *jb = JsonbValueToJsonb(jbv);
+
+ if (!out)
+ out = palloc(sizeof(*out));
+
+ return JsonbInitBinary(out, jb);
+}
+
+/*
+ * 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 inline int
+JsonbType(JsonbValue *jb)
+{
+ int type = jb->type;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = (void *) jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ type = jbvScalar;
+ else if (JsonContainerIsObject(jbc))
+ type = jbvObject;
+ else if (JsonContainerIsArray(jbc))
+ type = jbvArray;
+ else
+ elog(ERROR, "Unknown container type: 0x%08x", jbc->header);
+ }
+
+ return type;
+}
+
+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;
+}
+
+static inline JsonbValue *
+extractScalar(JsonbValue *scalar, JsonbValue *buf)
+{
+ if (scalar->type == jbvBinary &&
+ JsonContainerIsScalar(scalar->val.binary.data))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(scalar->val.binary.data, buf);
+ Assert(res);
+ return buf;
+ }
+
+ return scalar;
+}
+
+static inline JsonbValue *
+getScalar(JsonbValue *scalar, JsonbValue *buf, int type)
+{
+ scalar = extractScalar(scalar, buf);
+
+ return scalar->type == type ? scalar : NULL;
+}
+
+/* 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)))
+ {
+ JsonbValue bin;
+
+ jbv = extractScalar(jbv, jbv);
+
+ if (jbv->type == jbvObject || jbv->type == jbvArray)
+ jbv = JsonbWrapInBinary(jbv, &bin);
+
+ pushJsonbValue(&ps, WJB_ELEM, jbv);
+ }
+
+ return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
+}
+
+/********************Execute functions for JsonPath***************************/
+
+/*
+ * Find value of jsonpath variable in a list of passing params
+ */
+static int
+computeJsonPathVariable(JsonPathItem *variable, List *vars, JsonbValue *value)
+{
+ ListCell *cell;
+ JsonPathVariable *var = NULL;
+ bool isNull;
+ Datum computedValue;
+ char *varName;
+ int varNameLength;
+ int varId = 1;
+
+ 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;
+ varId++;
+ }
+
+ 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 varId;
+ }
+
+ 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 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.tz = 0;
+ value->val.datetime.value = computedValue;
+ break;
+ case JSONBOID:
+ {
+ Jsonb *jb = DatumGetJsonbP(computedValue);
+
+ if (JB_ROOT_IS_SCALAR(jb))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(&jb->root, value);
+ Assert(res);
+ }
+ else
+ JsonbInitBinary(value, jb);
+ }
+ 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")));
+ }
+
+ return varId;
+}
+
+/*
+ * Convert jsonpath's scalar or variable node to actual jsonb value.
+ *
+ * If node is a variable then its id returned, otherwise 0 returned.
+ */
+static int
+computeJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item, 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:
+ return computeJsonPathVariable(item, cxt->vars, value);
+ default:
+ elog(ERROR, "unexpected jsonpath item type");
+ }
+
+ return 0;
+}
+
+
+/*
+ * Get the type name of a SQL/JSON item.
+ */
+static const char *
+JsonbTypeName(JsonbValue *jb)
+{
+ JsonbValue jbvbuf;
+
+ if (jb->type == jbvBinary)
+ {
+ JsonbContainer *jbc = jb->val.binary.data;
+
+ if (JsonContainerIsScalar(jbc))
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ res = JsonbExtractScalar(jbc, &jbvbuf);
+ Assert(res);
+ jb = &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";
+ 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";
+ }
+}
+
+/*
+ * Returns the size of an array item, or -1 if item is not an array.
+ */
+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;
+}
+
+/*
+ * Compare two numerics.
+ */
+static int
+compareNumeric(Numeric a, Numeric b)
+{
+ return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
+ PointerGetDatum(a),
+ PointerGetDatum(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)
+{
+ 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));
+}
+
+/*
+ * Compare two SQL/JSON items using comparison operation 'op'.
+ */
+static JsonPathBool
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+{
+ int cmp;
+ bool res;
+
+ if (jb1->type != jb2->type)
+ {
+ if (jb1->type == jbvNull || jb2->type == jbvNull)
+
+ /*
+ * Equality and order comparison of nulls to non-nulls returns
+ * always false, but inequality comparison returns true.
+ */
+ return op == jpiNotEqual ? jpbTrue : jpbFalse;
+
+ /* Non-null items of different types are not comparable. */
+ return jpbUnknown;
+ }
+
+ switch (jb1->type)
+ {
+ case jbvNull:
+ cmp = 0;
+ break;
+ case jbvBool:
+ cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
+ jb1->val.boolean ? 1 : -1;
+ 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,
+ 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 jpbUnknown;
+ }
+ break;
+
+ case jbvBinary:
+ case jbvArray:
+ case jbvObject:
+ return jpbUnknown; /* non-scalars are not comparable */
+
+ default:
+ elog(ERROR, "invalid jsonb value type %d", jb1->type);
+ }
+
+ switch (op)
+ {
+ case jpiEqual:
+ res = (cmp == 0);
+ break;
+ case jpiNotEqual:
+ res = (cmp != 0);
+ break;
+ case jpiLess:
+ res = (cmp < 0);
+ break;
+ case jpiGreater:
+ res = (cmp > 0);
+ break;
+ case jpiLessOrEqual:
+ res = (cmp <= 0);
+ break;
+ case jpiGreaterOrEqual:
+ res = (cmp >= 0);
+ break;
+ default:
+ elog(ERROR, "Unknown jsonpath operation");
+ return jpbUnknown;
+ }
+
+ return res ? jpbTrue : jpbFalse;
+}
+
+/* Comparison predicate callback. */
+static inline JsonPathBool
+executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
+{
+ return compareItems(cmp->type, lv, rv);
+}
+
+static JsonbValue *
+copyJsonbValue(JsonbValue *src)
+{
+ JsonbValue *dst = palloc(sizeof(*dst));
+
+ *dst = *src;
+
+ return dst;
+}
+
+/*
+ * Execute next jsonpath item if it does exist.
+ */
+static inline JsonPathExecResult
+recursiveExecuteNext(JsonPathExecContext *cxt,
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *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)
+ JsonValueListAppend(found, copy ? copyJsonbValue(v) : v);
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ JsonValueList seq = {0};
+ JsonValueListIterator it = {0};
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &seq);
+ JsonbValue *item;
+
+ if (jperIsError(res))
+ return res;
+
+ while ((item = JsonValueListNext(&seq, &it)))
+ {
+ if (item->type == jbvArray)
+ {
+ JsonbValue *elem = item->val.array.elems;
+ JsonbValue *last = elem + item->val.array.nElems;
+
+ for (; elem < last; elem++)
+ JsonValueListAppend(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)
+ JsonValueListAppend(found, copyJsonbValue(&elem));
+ }
+ }
+ else
+ JsonValueListAppend(found, item);
+ }
+
+ return jperOk;
+ }
+
+ return recursiveExecute(cxt, jsp, jb, found);
+}
+
+typedef JsonPathBool (*JsonPathPredicateCallback) (JsonPathItem *jsp,
+ JsonbValue *larg,
+ JsonbValue *rarg,
+ void *param);
+
+/*
+ * Execute unary or binary predicate.
+ *
+ * Predicates have existence semantics, because their operands are item
+ * sequences. Pairs of items from the left and right operand's sequences are
+ * checked. TRUE returned only if any pair satisfying the condition is found.
+ * In strict mode, even if the desired pair has already been found, all pairs
+ * still need to be examined to check the absence of errors. If any error
+ * occurs, UNKNOWN (analogous to SQL NULL) is returned.
+ */
+static inline JsonPathBool
+executePredicate(JsonPathExecContext *cxt, JsonPathItem *pred,
+ JsonPathItem *larg, JsonPathItem *rarg, JsonbValue *jb,
+ bool unwrapRightArg, JsonPathPredicateCallback exec, void *param)
+{
+ JsonPathExecResult res;
+ JsonValueListIterator lseqit = {0};
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonbValue *lval;
+ bool error = false;
+ bool found = false;
+
+ /* Left argument is always auto-unwrapped. */
+ res = recursiveExecuteAndUnwrap(cxt, larg, jb, &lseq);
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ if (rarg)
+ {
+ /* Right argument is conditionally auto-unwrapped. */
+ res = unwrapRightArg ?
+ recursiveExecuteAndUnwrap(cxt, rarg, jb, &rseq) :
+ recursiveExecute(cxt, rarg, jb, &rseq);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+ }
+
+ while ((lval = JsonValueListNext(&lseq, &lseqit)))
+ {
+ JsonValueListIterator rseqit;
+ JsonbValue *rval;
+ int i = 0;
+
+ if (rarg)
+ memset(&rseqit, 0, sizeof(rseqit));
+ else
+ rval = NULL;
+
+ /* Loop only if we have right arg sequence. */
+ while (rarg ? !!(rval = JsonValueListNext(&rseq, &rseqit)) : !i++)
+ {
+ JsonPathBool res = exec(pred, lval, rval, param);
+
+ if (res == jpbUnknown)
+ {
+ if (jspStrictAbsenseOfErrors(cxt))
+ return jpbUnknown;
+
+ error = true;
+ }
+ else if (res == jpbTrue)
+ {
+ if (!jspStrictAbsenseOfErrors(cxt))
+ return jpbTrue;
+
+ found = true;
+ }
+ }
+ }
+
+ if (found) /* possible only in strict mode */
+ return jpbTrue;
+
+ if (error) /* possible only in lax mode */
+ return jpbUnknown;
+
+ return jpbFalse;
+}
+
+/*
+ * Execute binary arithmetic expression on singleton numeric operands.
+ * Array operands are automatically unwrapped in lax mode.
+ */
+static JsonPathExecResult
+executeBinaryArithmExpr(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ JsonPathExecResult jper;
+ JsonPathItem elem;
+ JsonValueList lseq = {0};
+ JsonValueList rseq = {0};
+ JsonbValue *lval;
+ JsonbValue *rval;
+ JsonbValue lvalbuf;
+ JsonbValue rvalbuf;
+ PGFunction func;
+ Datum ldatum;
+ Datum rdatum;
+ Datum res;
+ bool hasNext;
+
+ jspGetLeftArg(jsp, &elem);
+
+ /* XXX by standard unwrapped only operands of multiplicative expressions */
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
+
+ if (jper == jperOk)
+ {
+ jspGetRightArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &rseq); /* XXX */
+ }
+
+ if (jper != jperOk ||
+ JsonValueListLength(&lseq) != 1 ||
+ JsonValueListLength(&rseq) != 1 ||
+ !(lval = getScalar(JsonValueListHead(&lseq), &lvalbuf, jbvNumeric)) ||
+ !(rval = getScalar(JsonValueListHead(&rseq), &rvalbuf, jbvNumeric)))
+ return jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!found && !hasNext)
+ return jperOk;
+
+ ldatum = NumericGetDatum(lval->val.numeric);
+ rdatum = NumericGetDatum(rval->val.numeric);
+
+ switch (jsp->type)
+ {
+ case jpiAdd:
+ func = numeric_add;
+ break;
+ case jpiSub:
+ func = numeric_sub;
+ break;
+ case jpiMul:
+ func = numeric_mul;
+ break;
+ case jpiDiv:
+ func = numeric_div;
+ break;
+ case jpiMod:
+ func = numeric_mod;
+ break;
+ default:
+ elog(ERROR, "unknown jsonpath arithmetic operation %d", jsp->type);
+ func = NULL;
+ break;
+ }
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
+ * no function called inside performs data modification.
+ */
+ 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();
+
+ return jperMakeErrorData(edata);
+ }
+ PG_END_TRY();
+
+ lval = palloc(sizeof(*lval));
+ lval->type = jbvNumeric;
+ lval->val.numeric = DatumGetNumeric(res);
+
+ 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)
+{
+ JsonPathExecResult jper;
+ JsonPathExecResult jper2;
+ JsonPathItem elem;
+ JsonValueList seq = {0};
+ JsonValueListIterator it = {0};
+ JsonbValue *val;
+ bool hasNext;
+
+ jspGetArg(jsp, &elem);
+ jper = recursiveExecuteAndUnwrap(cxt, &elem, jb, &seq);
+
+ if (jperIsError(jper))
+ return jperReplace(jper, jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND));
+
+ jper = jperNotFound;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ while ((val = JsonValueListNext(&seq, &it)))
+ {
+ if ((val = getScalar(val, val, jbvNumeric)))
+ {
+ if (!found && !hasNext)
+ return jperOk;
+ }
+ else
+ {
+ if (!found && !hasNext)
+ continue; /* skip non-numerics processing */
+
+ return jperMakeError(ERRCODE_JSON_NUMBER_NOT_FOUND);
+ }
+
+ 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);
+ }
+
+ jper2 = recursiveExecuteNext(cxt, jsp, &elem, val, found, false);
+
+ if (jperIsError(jper2))
+ return jper2;
+
+ if (jper2 == jperOk)
+ {
+ if (!found)
+ return jperOk;
+ jper = jperOk;
+ }
+ }
+
+ return jper;
+}
+
+/*
+ * implements jpiAny node (** operator)
+ */
+static JsonPathExecResult
+recursiveAny(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *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);
+
+ /*
+ * Recursively iterate over jsonb objects/arrays
+ */
+ 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 ||
+ (first == PG_UINT32_MAX && last == PG_UINT32_MAX &&
+ 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;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ JsonbValue *jbv;
+ JsonValueList found = {0};
+ JsonbValue tmp;
+ JsonPathExecResult res = recursiveExecute(cxt, jsp, jb, &found);
+
+ if (jperIsError(res))
+ return res;
+
+ if (JsonValueListLength(&found) != 1 ||
+ !(jbv = getScalar(JsonValueListHead(&found), &tmp, jbvNumeric)))
+ return jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+
+ *index = DatumGetInt32(DirectFunctionCall1(numeric_int4,
+ DirectFunctionCall2(numeric_trunc,
+ NumericGetDatum(jbv->val.numeric),
+ Int32GetDatum(0))));
+
+ return jperOk;
+}
+
+/*
+ * STARTS_WITH predicate callback.
+ *
+ * Check if the 'whole' string starts from 'initial' string.
+ */
+static inline JsonPathBool
+executeStartsWith(JsonPathItem *jsp, JsonbValue *whole, JsonbValue *initial,
+ void *param)
+{
+ JsonbValue wholeBuf;
+ JsonbValue initialBuf;
+
+ if (!(whole = getScalar(whole, &wholeBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (!(initial = getScalar(initial, &initialBuf, jbvString)))
+ return jpbUnknown; /* error */
+
+ if (whole->val.string.len >= initial->val.string.len &&
+ !memcmp(whole->val.string.val,
+ initial->val.string.val,
+ initial->val.string.len))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/* Context for LIKE_REGEX execution. */
+typedef struct JsonLikeRegexContext
+{
+ text *regex;
+ int cflags;
+} JsonLikeRegexContext;
+
+/*
+ * LIKE_REGEX predicate callback.
+ *
+ * Check if the string matches regex pattern.
+ */
+static JsonPathBool
+executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
+ void *param)
+{
+ JsonLikeRegexContext *cxt = param;
+ JsonbValue strbuf;
+
+ if (!(str = getScalar(str, &strbuf, jbvString)))
+ return jpbUnknown;
+
+ /* Cache regex text and converted flags. */
+ if (!cxt->regex)
+ {
+ uint32 flags = jsp->content.like_regex.flags;
+
+ cxt->regex =
+ cstring_to_text_with_len(jsp->content.like_regex.pattern,
+ jsp->content.like_regex.patternlen);
+
+ /* Convert regex flags. */
+ cxt->cflags = REG_ADVANCED;
+
+ if (flags & JSP_REGEX_ICASE)
+ cxt->cflags |= REG_ICASE;
+ if (flags & JSP_REGEX_MLINE)
+ cxt->cflags |= REG_NEWLINE;
+ if (flags & JSP_REGEX_SLINE)
+ cxt->cflags &= ~REG_NEWLINE;
+ if (flags & JSP_REGEX_WSPACE)
+ cxt->cflags |= REG_EXPANDED;
+ }
+
+ if (RE_compile_and_execute(cxt->regex, str->val.string.val,
+ str->val.string.len,
+ cxt->cflags, DEFAULT_COLLATION_OID, 0, NULL))
+ return jpbTrue;
+
+ return jpbFalse;
+}
+
+/*
+ * 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(text *fmt, text *datetime, char *tzname, bool strict,
+ Datum *value, Oid *typid, int32 *typmod, int *tz)
+{
+ MemoryContext mcxt = CurrentMemoryContext;
+ bool ok = false;
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
+ * no function called inside performs data modification.
+ */
+ PG_TRY();
+ {
+ *value = to_datetime(datetime, fmt, tzname, strict, typid, typmod, tz);
+ ok = true;
+ }
+ PG_CATCH();
+ {
+ if (ERRCODE_TO_CATEGORY(geterrcode()) != ERRCODE_DATA_EXCEPTION)
+ PG_RE_THROW();
+
+ FlushErrorState();
+ MemoryContextSwitchTo(mcxt);
+ }
+ PG_END_TRY();
+
+ 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, JsonPathBool res)
+{
+ JsonPathItem next;
+ JsonbValue jbv;
+ bool hasNext = jspGetNext(jsp, &next);
+
+ if (!found && !hasNext)
+ return jperOk; /* found singleton boolean value */
+
+ if (res == jpbUnknown)
+ {
+ jbv.type = jbvNull;
+ }
+ else
+ {
+ jbv.type = jbvBool;
+ 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 larg;
+ JsonPathItem rarg;
+ 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, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbFalse)
+ return jpbFalse;
+
+ /*
+ * SQL/JSON says that we should check second arg in case of
+ * jperError
+ */
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbTrue ? res : res2;
+
+ case jpiOr:
+ jspGetLeftArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbTrue)
+ return jpbTrue;
+
+ jspGetRightArg(jsp, &rarg);
+ res2 = recursiveExecuteBool(cxt, &rarg, jb, false);
+
+ return res2 == jpbFalse ? res : res2;
+
+ case jpiNot:
+ jspGetArg(jsp, &larg);
+
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+
+ if (res == jpbUnknown)
+ return jpbUnknown;
+
+ return res == jpbTrue ? jpbFalse : jpbTrue;
+
+ case jpiIsUnknown:
+ jspGetArg(jsp, &larg);
+ res = recursiveExecuteBool(cxt, &larg, jb, false);
+ return res == jpbUnknown ? jpbTrue : jpbFalse;
+
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ jspGetLeftArg(jsp, &larg);
+ jspGetRightArg(jsp, &rarg);
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
+ executeComparison, NULL);
+
+ case jpiStartsWith: /* 'whole STARTS WITH initial' */
+ jspGetLeftArg(jsp, &larg); /* 'whole' */
+ jspGetRightArg(jsp, &rarg); /* 'initial' */
+ return executePredicate(cxt, jsp, &larg, &rarg, jb, false,
+ executeStartsWith, NULL);
+
+ case jpiLikeRegex: /* 'expr LIKE_REGEX pattern FLAGS flags' */
+ {
+ /*
+ * 'expr' is a sequence-returning expression. 'pattern' is a
+ * regex string literal. SQL/JSON standard requires XQuery
+ * regexes, but we use Postgres regexes here. 'flags' is a
+ * string literal converted to integer flags at compile-time.
+ */
+ JsonLikeRegexContext lrcxt = {0};
+
+ jspInitByBuffer(&larg, jsp->base, jsp->content.like_regex.expr);
+
+ return executePredicate(cxt, jsp, &larg, NULL, jb, false,
+ executeLikeRegex, &lrcxt);
+ }
+
+ case jpiExists:
+ jspGetArg(jsp, &larg);
+
+ 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, &larg, jb, &vals);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return JsonValueListIsEmpty(&vals) ? jpbFalse : jpbTrue;
+ }
+ else
+ {
+ JsonPathExecResult res = recursiveExecute(cxt, &larg, jb, NULL);
+
+ if (jperIsError(res))
+ return jperReplace(res, jpbUnknown);
+
+ return res == jperOk ? jpbTrue : jpbFalse;
+ }
+
+ default:
+ elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
+ return jpbUnknown;
+ }
+}
+
+/*
+ * Execute nested (filters etc.) boolean expression pushing current SQL/JSON
+ * item onto the stack.
+ */
+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)
+{
+ JsonbValue *v;
+ JsonbValue vbuf;
+ bool copy = true;
+
+ if (JsonbType(jbv) == jbvScalar)
+ {
+ bool res PG_USED_FOR_ASSERTS_ONLY;
+
+ if (jspHasNext(jsp))
+ v = &vbuf;
+ else
+ {
+ v = palloc(sizeof(*v));
+ copy = false;
+ }
+
+ res = JsonbExtractScalar(jbv->val.binary.data, v);
+ Assert(res);
+ }
+ else
+ v = jbv;
+
+ return recursiveExecuteNext(cxt, jsp, NULL, v, found, copy);
+}
+
+/* Save base object and its id needed for the execution of .keyvalue(). */
+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
+ * 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
+recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonPathItem elem;
+ JsonPathExecResult res = jperNotFound;
+ bool hasNext;
+ JsonBaseObjectInfo baseObject;
+
+ check_stack_depth();
+ CHECK_FOR_INTERRUPTS();
+
+ switch (jsp->type)
+ {
+ /* all boolean item types: */
+ case jpiAnd:
+ case jpiOr:
+ case jpiNot:
+ case jpiIsUnknown:
+ case jpiEqual:
+ case jpiNotEqual:
+ case jpiLess:
+ case jpiGreater:
+ case jpiLessOrEqual:
+ case jpiGreaterOrEqual:
+ case jpiExists:
+ case jpiStartsWith:
+ case jpiLikeRegex:
+ {
+ JsonPathBool st = recursiveExecuteBool(cxt, jsp, jb, true);
+
+ res = appendBoolResult(cxt, jsp, found, st);
+ break;
+ }
+
+ case jpiKey:
+ 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);
+
+ v = findJsonbValueFromContainer(jb->val.binary.data, JB_FOBJECT, &key);
+
+ if (v != NULL)
+ {
+ 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 (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_MEMBER_NOT_FOUND);
+ }
+ break;
+
+ case jpiRoot:
+ jb = cxt->root;
+ baseObject = setBaseObject(cxt, jb, 0);
+ res = recursiveExecuteBase(cxt, jsp, jb, found);
+ cxt->baseObject = baseObject;
+ break;
+
+ case jpiCurrent:
+ res = recursiveExecuteBase(cxt, jsp, cxt->stack->item, found);
+ break;
+
+ case jpiAnyArray:
+ if (JsonbType(jb) == jbvArray)
+ {
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *el = jb->val.array.elems;
+ JsonbValue *last_el = el + jb->val.array.nElems;
+
+ for (; el < last_el; el++)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, el, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ 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 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 || 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 */
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ for (i = 0; i < jsp->content.array.nelems; 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 (jperIsError(res))
+ break;
+
+ if (range)
+ {
+ res = getArrayIndex(cxt, &to, jb, &index_to);
+
+ if (jperIsError(res))
+ break;
+ }
+ else
+ index_to = index_from;
+
+ if (!jspIgnoreStructuralErrors(cxt) &&
+ (index_from < 0 ||
+ index_from > index_to ||
+ index_to >= size))
+ {
+ res = jperMakeError(ERRCODE_INVALID_JSON_SUBSCRIPT);
+ break;
+ }
+
+ if (index_from < 0)
+ index_from = 0;
+
+ if (index_to >= size)
+ index_to = size - 1;
+
+ res = jperNotFound;
+
+ for (index = index_from; index <= index_to; 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;
+
+ copy = false;
+ }
+ else
+ {
+ v = &jb->val.array.elems[index];
+ copy = true;
+ }
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, v, found,
+ copy);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+
+ cxt->innermostArraySize = innermostArraySize;
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ 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)));
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, lastjbv, found, hasNext);
+ }
+ break;
+ case jpiAnyKey:
+ if (JsonbType(jb) == jbvObject)
+ {
+ 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);
+
+ while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE)
+ {
+ if (r == WJB_VALUE)
+ {
+ res = recursiveExecuteNext(cxt, jsp, &elem, &v, found, true);
+
+ if (jperIsError(res))
+ break;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ else if (!jspIgnoreStructuralErrors(cxt))
+ {
+ Assert(found);
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ }
+ break;
+ case jpiAdd:
+ case jpiSub:
+ case jpiMul:
+ case jpiDiv:
+ case jpiMod:
+ res = executeBinaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiPlus:
+ case jpiMinus:
+ res = executeUnaryArithmExpr(cxt, jsp, jb, found);
+ break;
+ case jpiFilter:
+ {
+ JsonPathBool st;
+
+ jspGetArg(jsp, &elem);
+ st = recursiveExecuteBoolNested(cxt, &elem, jb);
+ if (st != jpbTrue)
+ res = jperNotFound;
+ else
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ break;
+ }
+ case jpiAny:
+ {
+ JsonbValue jbvbuf;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ /* 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;
+ }
+
+ 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 jpiNull:
+ case jpiBool:
+ case jpiNumeric:
+ case jpiString:
+ case jpiVariable:
+ {
+ JsonbValue vbuf;
+ JsonbValue *v;
+ bool hasNext = jspGetNext(jsp, &elem);
+ int id;
+
+ if (!hasNext && !found)
+ {
+ res = jperOk; /* skip evaluation */
+ break;
+ }
+
+ v = hasNext ? &vbuf : palloc(sizeof(*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:
+ {
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jbv, found, false);
+ }
+ break;
+ case jpiSize:
+ {
+ int size = JsonbArraySize(jb);
+
+ if (size < 0)
+ {
+ if (!jspAutoWrap(cxt))
+ {
+ if (!jspIgnoreStructuralErrors(cxt))
+ 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ break;
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ {
+ JsonbValue jbvbuf;
+
+ if ((jb = getScalar(jb, &jbvbuf, 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 = recursiveExecuteNext(cxt, jsp, NULL, jb, found, false);
+ }
+ else
+ res = jperMakeError(ERRCODE_NON_NUMERIC_JSON_ITEM);
+ }
+ break;
+ case jpiDouble:
+ {
+ JsonbValue jbv;
+ MemoryContext mcxt = CurrentMemoryContext;
+
+ jb = extractScalar(jb, &jbv);
+
+ /*
+ * It is safe to use here PG_TRY/PG_CATCH without
+ * subtransaction because no function called inside performs
+ * data modification.
+ */
+ 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)
+ res = recursiveExecuteNext(cxt, jsp, NULL, jb, found, true);
+ }
+ break;
+ case jpiDatetime:
+ {
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz;
+ bool hasNext;
+
+ res = jperMakeError(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION);
+
+ if (!(jb = getScalar(jb, &jbvbuf, jbvString)))
+ break;
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.args.left)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ char *tzname = NULL;
+
+ jspGetLeftArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ if (jsp->content.args.right)
+ {
+ JsonValueList tzlist = {0};
+ JsonPathExecResult tzres;
+ JsonbValue *tzjbv;
+
+ jspGetRightArg(jsp, &elem);
+ tzres = recursiveExecuteNoUnwrap(cxt, &elem, jb,
+ &tzlist);
+
+ if (jperIsError(tzres))
+ return tzres;
+
+ if (JsonValueListLength(&tzlist) != 1)
+ break;
+
+ tzjbv = JsonValueListHead(&tzlist);
+
+ if (tzjbv->type != jbvString)
+ break;
+
+ tzname = pnstrdup(tzjbv->val.string.val,
+ tzjbv->val.string.len);
+ }
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ if (tryToParseDatetime(template, datetime, tzname, false,
+ &value, &typid, &typmod, &tz))
+ res = jperOk;
+
+ if (tzname)
+ pfree(tzname);
+ }
+ else
+ {
+ /* Try to recognize one of ISO formats. */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS",
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS"
+ };
+
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = {0};
+ int i;
+
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ if (tryToParseDatetime(fmt_txt[i], datetime, NULL, true,
+ &value, &typid, &typmod, &tz))
+ {
+ res = jperOk;
+ break;
+ }
+ }
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ break;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ break;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ res = recursiveExecuteNext(cxt, jsp, &elem, jb, found, hasNext);
+ }
+ break;
+ case jpiKeyValue:
+
+ /*
+ * .keyvalue() method returns a sequence of object's key-value
+ * pairs in the following format: '{ "key": key, "value": value,
+ * "id": id }'.
+ *
+ * "id" field is an object identifier which is constructed from
+ * the two parts: base object id and its binary offset in base
+ * object's jsonb: id = 10000000000 * base_object_id +
+ * obj_offset_in_base_object
+ *
+ * 10000000000 (10^10) -- is a first round decimal number greater
+ * than 2^32 (maximal offset in jsonb). Decimal multiplier is
+ * used here to improve the readability of identifiers.
+ *
+ * Base object is usually a root object of the path: context item
+ * '$' or path variable '$var', literals can't produce objects for
+ * now. But if the path contains generated objects (.keyvalue()
+ * itself, for example), then they become base object for the
+ * subsequent .keyvalue().
+ *
+ * Id of '$' is 0. Id of '$var' is its ordinal (positive) number
+ * in the list of variables (see computeJsonPathVariable()). Ids
+ * for generated objects are assigned using global counter
+ * JsonPathExecContext.generatedObjectId starting from
+ * (number_of_vars + 1).
+ */
+
+ if (JsonbType(jb) != jbvObject)
+ res = jperMakeError(ERRCODE_JSON_OBJECT_NOT_FOUND);
+ else
+ {
+ int32 r;
+ 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);
+
+ if (jb->type == jbvBinary
+ ? !JsonContainerSize(jb->val.binary.data)
+ : !jb->val.object.nPairs)
+ {
+ 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;
+
+ idstr.type = jbvString;
+ idstr.val.string.val = "id";
+ idstr.val.string.len = 2;
+
+ if (jb->type == jbvObject)
+ jb = JsonbWrapInBinary(jb, &bin);
+
+ /* construct object id from its base object and offset in it */
+ 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)
+ {
+ 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);
+
+ 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;
+
+ if (res == jperOk && !found)
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
+ }
+
+ 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)
+{
+ JsonPathExecResult res = jperNotFound;
+
+ if (jb->type == jbvArray)
+ {
+ JsonbValue *elem = jb->val.array.elems;
+ JsonbValue *last = elem + jb->val.array.nElems;
+
+ 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;
+ }
+ }
+ }
+
+ 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)
+{
+ if (jspAutoUnwrap(cxt) && JsonbType(jb) == jbvArray)
+ return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+/*
+ * Execute jsonpath with automatic unwrapping of current item in lax mode.
+ */
+static inline JsonPathExecResult
+recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found)
+{
+ if (jspAutoUnwrap(cxt))
+ {
+ switch (jsp->type)
+ {
+ case jpiKey:
+ case jpiAnyKey:
+ /* case jpiAny: */
+ case jpiFilter:
+ /* all methods excluding type() and size() */
+ case jpiAbs:
+ case jpiFloor:
+ case jpiCeiling:
+ case jpiDouble:
+ case jpiDatetime:
+ case jpiKeyValue:
+ return recursiveExecuteUnwrap(cxt, jsp, jb, found);
+
+ default:
+ break;
+ }
+ }
+
+ return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
+}
+
+
+/*
+ * Interface to jsonpath executor
+ */
+static JsonPathExecResult
+executeJsonPath(JsonPath *path, List *vars, Jsonb *json, JsonValueList *foundJson)
+{
+ JsonPathExecContext cxt;
+ JsonPathItem jsp;
+ JsonbValue jbv;
+ JsonItemStackEntry root;
+
+ jspInit(&jsp, path);
+
+ cxt.vars = vars;
+ 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)
+ {
+ /*
+ * 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, &jsp, &jbv, &vals);
+
+ if (jperIsError(res))
+ return res;
+
+ return JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
+ }
+
+ return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
+}
+
+static Datum
+returnDATUM(void *arg, bool *isNull)
+{
+ *isNull = false;
+ return PointerGetDatum(arg);
+}
+
+static Datum
+returnNULL(void *arg, bool *isNull)
+{
+ *isNull = true;
+ return Int32GetDatum(0);
+}
+
+/*
+ * Convert jsonb object into list of vars for executor
+ */
+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 void
+throwJsonPathError(JsonPathExecResult res)
+{
+ int err;
+
+ if (!jperIsError(res))
+ return;
+
+ if (jperIsErrorData(res))
+ ThrowErrorData(jperGetErrorData(res));
+
+ err = jperGetError(res);
+
+ switch (err)
+ {
+ case ERRCODE_JSON_ARRAY_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON array not found")));
+ break;
+ case ERRCODE_JSON_OBJECT_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON object not found")));
+ break;
+ case ERRCODE_JSON_MEMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON member not found")));
+ break;
+ case ERRCODE_JSON_NUMBER_NOT_FOUND:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON number not found")));
+ break;
+ case ERRCODE_JSON_SCALAR_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("SQL/JSON scalar required")));
+ break;
+ case ERRCODE_SINGLETON_JSON_ITEM_REQUIRED:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Singleton SQL/JSON item required")));
+ break;
+ case ERRCODE_NON_NUMERIC_JSON_ITEM:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Non-numeric SQL/JSON item")));
+ break;
+ case ERRCODE_INVALID_JSON_SUBSCRIPT:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid SQL/JSON subscript")));
+ break;
+ case ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Invalid argument for SQL/JSON datetime function")));
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(err),
+ errmsg("Unknown SQL/JSON error")));
+ break;
+ }
+}
+
+/*
+ * jsonb_path_exists
+ * Returns true if jsonpath returns at least one item for the specified
+ * jsonb value.
+ */
+Datum
+jsonb_path_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, vars, jb, NULL);
+
+ PG_FREE_IF_COPY(jb, 0);
+ PG_FREE_IF_COPY(jp, 1);
+
+ if (jperIsError(res))
+ {
+ jperFree(res);
+ PG_RETURN_NULL();
+ }
+
+ PG_RETURN_BOOL(res == jperOk);
+}
+
+/*
+ * jsonb_path_exists_novars
+ * Implements the 2-argument version of jsonb_path_exists
+ */
+Datum
+jsonb_path_exists_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_exists(fcinfo);
+}
+
+/*
+ * jsonb_path_match
+ * Returns jsonpath predicate result item for the specified jsonb value.
+ * The result of jsonpath execution should be a single item, error is
+ * raised otherwise. Non-bool result is treated as NULL.
+ */
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonbValue *jbv;
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NULL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) != 1)
+ throwJsonPathError(jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED));
+
+ jbv = JsonValueListHead(&found);
+ jbv = extractScalar(jbv, 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);
+}
+
+/*
+ * jsonb_path_match_novars
+ * Implements the 2-argument version of jsonb_path_match
+ */
+Datum
+jsonb_path_match_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_match(fcinfo);
+}
+
+/*
+ * jsonb_path_query
+ * Executes jsonpath for given jsonb document and returns result as
+ * rowset.
+ */
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *found;
+ JsonbValue *v;
+ ListCell *c;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ Jsonb *jb;
+ JsonPathExecResult res;
+ MemoryContext oldcontext;
+ List *vars = NIL;
+ JsonValueList found = {0};
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ jb = PG_GETARG_JSONB_P_COPY(0);
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_FREE_IF_COPY(jp, 1);
+
+ funcctx->user_fctx = JsonValueListGetList(&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)));
+}
+
+/*
+ * jsonb_path_query_novars
+ * Implements the 2-argument version of jsonb_path_query
+ */
+Datum
+jsonb_path_query_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query(fcinfo);
+}
+
+/*
+ * jsonb_path_query_array
+ * Executes jsonpath for given jsonb document and returns result as
+ * jsonb array.
+ */
+Datum
+jsonb_path_query_array(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
+}
+
+/*
+ * jsonb_path_query_array_novars
+ * Implements the 2-argument version of jsonb_path_query_array
+ */
+Datum
+jsonb_path_query_array_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_array(fcinfo);
+}
+
+/*
+ * jsonb_path_query_first
+ * Executes jsonpath for given jsonb document and returns first result
+ * item. If there are no items, NULL returned.
+ */
+Datum
+jsonb_path_query_first(FunctionCallInfo fcinfo)
+{
+ Jsonb *jb = PG_GETARG_JSONB_P(0);
+ JsonPath *jp = PG_GETARG_JSONPATH_P(1);
+ JsonValueList found = {0};
+ JsonPathExecResult res;
+ List *vars;
+
+ if (PG_NARGS() == 3)
+ vars = makePassingVars(PG_GETARG_JSONB_P(2));
+ else
+ vars = NIL;
+
+ res = executeJsonPath(jp, vars, jb, &found);
+
+ if (jperIsError(res))
+ throwJsonPathError(res);
+
+ if (JsonValueListLength(&found) >= 1)
+ PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
+ else
+ PG_RETURN_NULL();
+}
+
+/*
+ * jsonb_path_query_first_novars
+ * Implements the 2-argument version of jsonb_path_query_first
+ */
+Datum
+jsonb_path_query_first_novars(PG_FUNCTION_ARGS)
+{
+ /* just call the other one -- it can handle both cases */
+ return jsonb_path_query_first(fcinfo);
+}
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
new file mode 100644
index 0000000..cf648c3
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -0,0 +1,493 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_gram.y
+ * Grammar definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/jsonpath_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+%{
+#include "postgres.h"
+
+#include "catalog/pg_collation.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/pg_list.h"
+#include "regex/regex.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->value.string.val = s->val;
+ v->value.string.len = s->len;
+ }
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemVariable(string *s)
+{
+ JsonPathParseItem *v;
+
+ v = makeItemType(jpiVariable);
+ v->value.string.val = s->val;
+ v->value.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->value.numeric =
+ DatumGetNumeric(DirectFunctionCall3(numeric_in, CStringGetDatum(s->val), 0, -1));
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBool(bool val) {
+ JsonPathParseItem *v = makeItemType(jpiBool);
+
+ v->value.boolean = val;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemBinary(int type, JsonPathParseItem* la, JsonPathParseItem *ra)
+{
+ JsonPathParseItem *v = makeItemType(type);
+
+ v->value.args.left = la;
+ v->value.args.right = ra;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemUnary(int type, JsonPathParseItem* a)
+{
+ JsonPathParseItem *v;
+
+ if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ return a;
+
+ if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ {
+ v = makeItemType(jpiNumeric);
+ v->value.numeric =
+ DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
+ NumericGetDatum(a->value.numeric)));
+ return v;
+ }
+
+ v = makeItemType(type);
+
+ v->value.arg = a;
+
+ return v;
+}
+
+static JsonPathParseItem*
+makeItemList(List *list)
+{
+ JsonPathParseItem *head, *end;
+ ListCell *cell = list_head(list);
+
+ head = end = (JsonPathParseItem *) lfirst(cell);
+
+ if (!lnext(cell))
+ return head;
+
+ /* 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;
+ }
+
+ return head;
+}
+
+static JsonPathParseItem*
+makeIndexArray(List *list)
+{
+ JsonPathParseItem *v = makeItemType(jpiIndexArray);
+ ListCell *cell;
+ int i = 0;
+
+ Assert(list_length(list) > 0);
+ v->value.array.nelems = list_length(list);
+
+ v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) * v->value.array.nelems);
+
+ foreach(cell, list)
+ {
+ 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;
+}
+
+static JsonPathParseItem*
+makeAny(int first, int last)
+{
+ JsonPathParseItem *v = makeItemType(jpiAny);
+
+ v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
+ v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
+
+ 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 */
+%pure-parser
+%expect 0
+%name-prefix="jsonpath_yy"
+%error-verbose
+%parse-param {JsonPathParseResult **result}
+
+%union {
+ string str;
+ List *elems; /* list of JsonPathParseItem */
+ List *indexs; /* list of integers */
+ JsonPathParseItem *value;
+ JsonPathParseResult *result;
+ JsonPathItemType optype;
+ bool boolean;
+ int integer;
+}
+
+%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
+%token <str> OR_P AND_P NOT_P
+%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
+%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
+%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
+%token <str> KEYVALUE_P
+
+%type <result> result
+
+%type <value> scalar_value path_primary expr array_accessor
+ any_path accessor_op key predicate delimited_predicate
+ index_elem starts_with_initial datetime_template opt_datetime_template
+ expr_or_predicate
+
+%type <elems> accessor_expr
+
+%type <indexs> index_list
+
+%type <optype> comp_op method
+
+%type <boolean> mode
+
+%type <str> key_name
+
+%type <integer> any_level
+
+%left OR_P
+%left AND_P
+%right NOT_P
+%left '+' '-'
+%left '*' '/' '%'
+%left UMINUS
+%nonassoc '(' ')'
+
+/* Grammar follows */
+%%
+
+result:
+ mode expr_or_predicate {
+ *result = palloc(sizeof(JsonPathParseResult));
+ (*result)->expr = $2;
+ (*result)->lax = $1;
+ }
+ | /* EMPTY */ { *result = NULL; }
+ ;
+
+expr_or_predicate:
+ expr { $$ = $1; }
+ | predicate { $$ = $1; }
+ ;
+
+mode:
+ STRICT_P { $$ = false; }
+ | LAX_P { $$ = true; }
+ | /* EMPTY */ { $$ = true; }
+ ;
+
+scalar_value:
+ STRING_P { $$ = makeItemString(&$1); }
+ | NULL_P { $$ = makeItemString(NULL); }
+ | TRUE_P { $$ = makeItemBool(true); }
+ | FALSE_P { $$ = makeItemBool(false); }
+ | NUMERIC_P { $$ = makeItemNumeric(&$1); }
+ | INT_P { $$ = makeItemNumeric(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+comp_op:
+ EQUAL_P { $$ = jpiEqual; }
+ | NOTEQUAL_P { $$ = jpiNotEqual; }
+ | LESS_P { $$ = jpiLess; }
+ | GREATER_P { $$ = jpiGreater; }
+ | LESSEQUAL_P { $$ = jpiLessOrEqual; }
+ | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
+ ;
+
+delimited_predicate:
+ '(' predicate ')' { $$ = $2; }
+ | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
+ ;
+
+predicate:
+ delimited_predicate { $$ = $1; }
+ | 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); }
+ | expr STARTS_P WITH_P starts_with_initial
+ { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
+ | expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
+ | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
+ { $$ = makeItemLikeRegex($1, &$3, &$5); }
+ ;
+
+starts_with_initial:
+ STRING_P { $$ = makeItemString(&$1); }
+ | VARIABLE_P { $$ = makeItemVariable(&$1); }
+ ;
+
+path_primary:
+ scalar_value { $$ = $1; }
+ | '$' { $$ = makeItemType(jpiRoot); }
+ | '@' { $$ = makeItemType(jpiCurrent); }
+ | LAST_P { $$ = makeItemType(jpiLast); }
+ ;
+
+accessor_expr:
+ path_primary { $$ = list_make1($1); }
+ | '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
+ | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
+ | accessor_expr accessor_op { $$ = lappend($1, $2); }
+ ;
+
+expr:
+ 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:
+ expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
+ | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
+ ;
+
+index_list:
+ index_elem { $$ = list_make1($1); }
+ | index_list ',' index_elem { $$ = lappend($1, $3); }
+ ;
+
+array_accessor:
+ '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
+ | '[' index_list ']' { $$ = makeIndexArray($2); }
+ ;
+
+any_level:
+ INT_P { $$ = pg_atoi($1.val, 4, 0); }
+ | LAST_P { $$ = -1; }
+ ;
+
+any_path:
+ 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:
+ '.' key { $$ = $2; }
+ | '.' '*' { $$ = makeItemType(jpiAnyKey); }
+ | array_accessor { $$ = $1; }
+ | '.' any_path { $$ = $2; }
+ | '.' method '(' ')' { $$ = makeItemType($2); }
+ | '.' DATETIME_P '(' opt_datetime_template ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, NULL); }
+ | '.' DATETIME_P '(' datetime_template ',' expr ')'
+ { $$ = makeItemBinary(jpiDatetime, $4, $6); }
+ | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
+ ;
+
+datetime_template:
+ STRING_P { $$ = makeItemString(&$1); }
+ ;
+
+opt_datetime_template:
+ datetime_template { $$ = $1; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+key:
+ key_name { $$ = makeItemKey(&$1); }
+ ;
+
+key_name:
+ IDENT_P
+ | 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
+ | LAST_P
+ | STARTS_P
+ | WITH_P
+ | LIKE_REGEX_P
+ | FLAG_P
+ ;
+
+method:
+ ABS_P { $$ = jpiAbs; }
+ | SIZE_P { $$ = jpiSize; }
+ | TYPE_P { $$ = jpiType; }
+ | FLOOR_P { $$ = jpiFloor; }
+ | DOUBLE_P { $$ = jpiDouble; }
+ | CEILING_P { $$ = jpiCeiling; }
+ | KEYVALUE_P { $$ = jpiKeyValue; }
+ ;
+%%
+
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
new file mode 100644
index 0000000..89e7f93
--- /dev/null
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -0,0 +1,630 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scan.l
+ * Lexical parser for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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);
+static void parseHexChars(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 xVARQUOTED
+%x xSINGLEQUOTED
+%x xCOMMENT
+
+special [\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/]
+any [^\?\%\$\.\[\]\{\}\(\)\|\&\!\=\<\>\@\#\,\*:\-\+\/\\\"\' \t\n\r\f]
+blank [ \t\n\r\f]
+hex_dig [0-9A-Fa-f]
+unicode \\u({hex_dig}{4}|\{{hex_dig}{1,6}\})
+hex_char \\x{hex_dig}{2}
+
+
+%%
+
+<INITIAL>\&\& { return AND_P; }
+
+<INITIAL>\|\| { return OR_P; }
+
+<INITIAL>\! { return NOT_P; }
+
+<INITIAL>\*\* { return ANY_P; }
+
+<INITIAL>\< { return LESS_P; }
+
+<INITIAL>\<\= { return LESSEQUAL_P; }
+
+<INITIAL>\=\= { return EQUAL_P; }
+
+<INITIAL>\<\> { return NOTEQUAL_P; }
+
+<INITIAL>\!\= { return NOTEQUAL_P; }
+
+<INITIAL>\>\= { return GREATEREQUAL_P; }
+
+<INITIAL>\> { return GREATER_P; }
+
+<INITIAL>\${any}+ {
+ addstring(true, yytext + 1, yyleng - 1);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return VARIABLE_P;
+ }
+
+<INITIAL>\$\" {
+ addchar(true, '\0');
+ BEGIN xVARQUOTED;
+ }
+
+<INITIAL>{special} { return *yytext; }
+
+<INITIAL>{blank}+ { /* ignore */ }
+
+<INITIAL>\/\* {
+ addchar(true, '\0');
+ BEGIN xCOMMENT;
+ }
+
+<INITIAL>[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>\.[0-9]+[eE][+-]?[0-9]+ /* float */ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>([0-9]+)?\.[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return NUMERIC_P;
+ }
+
+<INITIAL>[0-9]+ {
+ addstring(true, yytext, yyleng);
+ addchar(false, '\0');
+ yylval->str = scanstring;
+ return INT_P;
+ }
+
+<INITIAL>{any}+ {
+ addstring(true, yytext, yyleng);
+ BEGIN xNONQUOTED;
+ }
+
+<INITIAL>\" {
+ addchar(true, '\0');
+ BEGIN xQUOTED;
+ }
+
+<INITIAL>\' {
+ addchar(true, '\0');
+ BEGIN xSINGLEQUOTED;
+ }
+
+<INITIAL>\\ {
+ yyless(0);
+ addchar(true, '\0');
+ BEGIN xNONQUOTED;
+ }
+
+<xNONQUOTED>{any}+ {
+ addstring(false, yytext, yyleng);
+ }
+
+<xNONQUOTED>{blank}+ {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+
+<xNONQUOTED>\/\* {
+ yylval->str = scanstring;
+ BEGIN xCOMMENT;
+ }
+
+<xNONQUOTED>({special}|\"|\') {
+ yylval->str = scanstring;
+ yyless(0);
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED><<EOF>> {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return checkSpecialVal();
+ }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\[\"\'\\] { addchar(false, yytext[1]); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\b { addchar(false, '\b'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\f { addchar(false, '\f'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\n { addchar(false, '\n'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\r { addchar(false, '\r'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\t { addchar(false, '\t'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\v { addchar(false, '\v'); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{unicode}+ { parseUnicode(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>{hex_char}+ { parseHexChars(yytext, yyleng); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\x { yyerror(NULL, "Hex character sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\u { yyerror(NULL, "Unicode sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\. { yyerror(NULL, "Escape sequence is invalid"); }
+
+<xNONQUOTED,xQUOTED,xVARQUOTED,xSINGLEQUOTED>\\ { yyerror(NULL, "Unexpected end after backslash"); }
+
+<xQUOTED,xVARQUOTED,xSINGLEQUOTED><<EOF>> { yyerror(NULL, "Unexpected end of quoted string"); }
+
+<xQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xVARQUOTED>\" {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return VARIABLE_P;
+ }
+
+<xSINGLEQUOTED>\' {
+ yylval->str = scanstring;
+ BEGIN INITIAL;
+ return STRING_P;
+ }
+
+<xQUOTED,xVARQUOTED>[^\\\"]+ { addstring(false, yytext, yyleng); }
+
+<xSINGLEQUOTED>[^\\\']+ { addstring(false, yytext, yyleng); }
+
+<INITIAL><<EOF>> { yyterminate(); }
+
+<xCOMMENT>\*\/ { BEGIN INITIAL; }
+
+<xCOMMENT>[^\*]+ { }
+
+<xCOMMENT>\* { }
+
+<xCOMMENT><<EOF>> { yyerror(NULL, "Unexpected end of comment"); }
+
+%%
+
+void
+jsonpath_yyerror(JsonPathParseResult **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, IS_P, "is"},
+ { 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"},
+ { 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"},
+ { 8, false, DATETIME_P, "datetime"},
+ { 8, false, KEYVALUE_P, "keyvalue"},
+ { 10,false, LIKE_REGEX_P, "like_regex"},
+};
+
+static int
+checkSpecialVal()
+{
+ int res = IDENT_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++;
+}
+
+JsonPathParseResult *
+parsejsonpath(const char *str, int len)
+{
+ JsonPathParseResult *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 */
+}
+
+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
+ */
+static void
+parseUnicode(char *s, int l)
+{
+ int i;
+ int hi_surrogate = -1;
+
+ for (i = 2; i < l; i += 2) /* skip '\u' */
+ {
+ int ch = 0;
+ int j;
+
+ if (s[i] == '{') /* parse '\u{XX...}' */
+ {
+ while (s[++i] != '}' && i < l)
+ ch = (ch << 4) | hexval(s[i]);
+ i++; /* ski p '}' */
+ }
+ else /* parse '\uXXXX' */
+ {
+ for (j = 0; j < 4 && i < l; j++)
+ ch = (ch << 4) | hexval(s[i++]);
+ }
+
+ addUnicode(ch, &hi_surrogate);
+ }
+
+ 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.")));
+ }
+}
+
+static void
+parseHexChars(char *s, int l)
+{
+ int i;
+
+ 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]);
+
+ addUnicodeChar(ch);
+ }
+}
+
+/*
+ * 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/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 4ef8a92..da13a87 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/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 4f7b9b6..7287653 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
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 06aec07..1c7af92 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -3255,5 +3255,13 @@
{ oid => '3287', descr => 'delete path',
oprname => '#-', oprleft => 'jsonb', oprright => '_text',
oprresult => 'jsonb', oprcode => 'jsonb_delete_path' },
+{ oid => '6076', descr => 'jsonpath exists',
+ oprname => '@?', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_exists(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
+{ oid => '6107', descr => 'jsonpath match',
+ oprname => '@@', oprleft => 'jsonb', oprright => 'jsonpath',
+ oprresult => 'bool', oprcode => 'jsonb_path_match(jsonb,jsonpath)',
+ oprrest => 'contsel', oprjoin => 'contjoinsel' },
]
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3ecc2e1..00e254b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9165,6 +9165,47 @@
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 => 'implementation of @? operator',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_exists_novars' },
+{ oid => '6055', descr => 'jsonpath query without variables',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath',
+ prosrc => 'jsonb_path_query_novars' },
+{ oid => '6124', descr => 'jsonpath query without variables wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_array_novars' },
+{ oid => '6122', descr => 'jsonpath query without variables first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_query_first_novars' },
+{ oid => '6073', descr => 'implementation of @@ operator',
+ proname => 'jsonb_path_match', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath', prosrc => 'jsonb_path_match_novars' },
+{ oid => '6056', descr => 'jsonpath exists test',
+ proname => 'jsonb_path_exists', prorettype => 'bool',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_exists' },
+{ oid => '6057', descr => 'jsonpath query',
+ proname => 'jsonb_path_query', prorows => '1000', proretset => 't',
+ prorettype => 'jsonb', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query' },
+{ oid => '6125', descr => 'jsonpath query wrapped into array',
+ proname => 'jsonb_path_query_array', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_query_array' },
+{ oid => '6123', descr => 'jsonpath query first item',
+ proname => 'jsonb_path_query_first', prorettype => 'jsonb',
+ proargtypes => 'jsonb jsonpath jsonb', prosrc => 'jsonb_path_query_first' },
+{ oid => '6074', descr => 'jsonpath match', proname => 'jsonb_path_match',
+ prorettype => 'bool', proargtypes => 'jsonb jsonpath jsonb',
+ prosrc => 'jsonb_path_match' },
+
# 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 4b7750d..e44c562 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/regex/regex.h b/src/include/regex/regex.h
index 27fdc09..23ad03d 100644
--- a/src/include/regex/regex.h
+++ b/src/include/regex/regex.h
@@ -167,10 +167,18 @@ typedef struct
/*
* the prototypes for exported functions
*/
+
+/* regcomp.c */
extern int pg_regcomp(regex_t *, const pg_wchar *, size_t, int, Oid);
extern int pg_regexec(regex_t *, const pg_wchar *, size_t, size_t, rm_detail_t *, size_t, regmatch_t[], int);
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);
+/* regexp.c */
+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/.gitignore b/src/include/utils/.gitignore
index 05cfa7a..e0705e1 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/jsonapi.h b/src/include/utils/jsonapi.h
index b9993a9..9eda9a3 100644
--- a/src/include/utils/jsonapi.h
+++ b/src/include/utils/jsonapi.h
@@ -161,6 +161,7 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
extern text *transform_json_string_values(text *json, void *action_state,
JsonTransformStringValuesAction transform_action);
-extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid);
+extern char *JsonEncodeDateTime(char *buf, Datum value, Oid typid,
+ const int *tzp);
#endif /* JSONAPI_H */
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index 6ccacf5..55c8d6a 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;
@@ -236,7 +238,15 @@ enum jbvType
jbvArray = 0x10,
jbvObject,
/* Binary (i.e. struct Jsonb) jbvArray/jbvObject */
- jbvBinary
+ jbvBinary,
+
+ /*
+ * Virtual types.
+ *
+ * These types are used only for in-memory JSON processing and serialized
+ * into JSON strings when outputted to json/jsonb.
+ */
+ jbvDatetime = 0x20,
};
/*
@@ -277,11 +287,20 @@ struct JsonbValue
int len;
JsonbContainer *data;
} binary; /* Array or object, in on-disk format */
+
+ struct
+ {
+ Datum value;
+ Oid typid;
+ int32 typmod;
+ int tz;
+ } datetime;
} val;
};
-#define IsAJsonbScalar(jsonbval) ((jsonbval)->type >= jbvNull && \
- (jsonbval)->type <= jbvBool)
+#define IsAJsonbScalar(jsonbval) (((jsonbval)->type >= jbvNull && \
+ (jsonbval)->type <= jbvBool) || \
+ (jsonbval)->type == jbvDatetime)
/*
* Key/value pair within an Object.
@@ -378,6 +397,6 @@ extern char *JsonbToCString(StringInfo out, JsonbContainer *in,
int estimated_len);
extern char *JsonbToCStringIndent(StringInfo out, JsonbContainer *in,
int estimated_len);
-
+extern bool 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 0000000..035eff5
--- /dev/null
+++ b/src/include/utils/jsonpath.h
@@ -0,0 +1,300 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath.h
+ * Definitions for jsonpath datatype
+ *
+ * Copyright (c) 2019, 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!) */
+ 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)))
+#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, /* 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 */
+#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.
+ * 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
+ */
+
+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
+ {
+ /* 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;
+ struct
+ {
+ int32 from;
+ int32 to;
+ } *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;
+
+ struct
+ {
+ int32 expr;
+ char *pattern;
+ int32 patternlen;
+ uint32 flags;
+ } like_regex;
+ } 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);
+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);
+extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
+ JsonPathItem *to, int i);
+
+/*
+ * Parsing
+ */
+
+typedef struct JsonPathParseItem JsonPathParseItem;
+
+struct JsonPathParseItem
+{
+ JsonPathItemType type;
+ 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;
+
+ /* storage for jpiIndexArray: indexes of array */
+ struct
+ {
+ int nelems;
+ struct
+ {
+ JsonPathParseItem *from;
+ JsonPathParseItem *to;
+ } *elems;
+ } array;
+
+ /* jpiAny: levels */
+ struct
+ {
+ uint32 first;
+ 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;
+ struct
+ {
+ uint32 len;
+ char *val; /* could not be not null-terminated */
+ } string;
+ } value;
+};
+
+typedef struct JsonPathParseResult
+{
+ JsonPathParseItem *expr;
+ bool lax;
+} JsonPathParseResult;
+
+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;
+
+/* 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)
+{
+ ErrorData *edata = palloc0(sizeof(*edata));
+
+ edata->sqlerrcode = sqlerrcode;
+
+ return edata;
+}
+
+typedef Datum (*JsonPathVariable_cb) (void *, bool *);
+
+typedef struct JsonPathVariable
+{
+ text *varName;
+ Oid typid;
+ int32 typmod;
+ JsonPathVariable_cb cb;
+ void *cb_arg;
+} JsonPathVariable;
+
+typedef struct JsonValueList
+{
+ JsonbValue *singleton;
+ List *list;
+} JsonValueList;
+
+#endif
diff --git a/src/include/utils/jsonpath_scanner.h b/src/include/utils/jsonpath_scanner.h
new file mode 100644
index 0000000..bbdd984
--- /dev/null
+++ b/src/include/utils/jsonpath_scanner.h
@@ -0,0 +1,32 @@
+/*-------------------------------------------------------------------------
+ *
+ * jsonpath_scanner.h
+ * Definitions for jsonpath scanner & parser
+ *
+ * Portions Copyright (c) 2019, 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 int jsonpath_yyparse(JsonPathParseResult **result);
+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
new file mode 100644
index 0000000..f8200d9
--- /dev/null
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -0,0 +1,1765 @@
+select jsonb '{"a": 12}' @? '$.a.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 12}' @? '$.b';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.a.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"b": {"a": 12}}' @? '$.*.a';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{}' @? '$.*';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": 1}' @? '$.*';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{1}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{2}';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? 'lax $.**{3}';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[]' @? '$[*]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? '$[*]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb_path_query('[1]', 'strict $[1]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb '[1]' @? '$[0]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.3]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.5]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[0.9]';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1]' @? '$[1.2]';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1]' @? 'strict $[1.2]';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] > @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,5]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,"5"]}' @? 'strict $ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": [1,2,3], "b": [3,4,null]}' @? '$ ? (@.a[*] >= @.b[*])';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == "1") is unknown)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '1' @? '$ ? ((@ == 1) is unknown)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[{"a": 1}, {"a": 2}]' @? '$[0 to 1] ? (@.a > 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+ jsonb_path_query
+------------------
+ 12
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+ jsonb_path_query
+------------------
+ {"a": 13}
+(1 row)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+ jsonb_path_query
+------------------
+ 12
+ {"a": 13}
+(2 rows)
+
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+ jsonb_path_query
+------------------
+ 13
+ 14
+(2 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+ jsonb_path_query
+------------------
+ {"a": 13}
+ {"b": 14}
+ "ccc"
+(3 rows)
+
+select jsonb_path_query('1', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('1', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[0]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[]', '$[last]');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $[last]');
+ERROR: Invalid SQL/JSON subscript
+select jsonb_path_query('[1]', '$[last]');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+ jsonb_path_query
+------------------
+ 2
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+ jsonb_path_query
+------------------
+ 3
+(1 row)
+
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+ERROR: Invalid SQL/JSON subscript
+select * from jsonb_path_query('{"a": 10}', '$');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+ERROR: could not find jsonpath variable 'value'
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ {"a": 10}
+(1 row)
+
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+(1 row)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+(2 rows)
+
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+ jsonb_path_query
+------------------
+ 10
+ 11
+ 12
+(3 rows)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+ jsonb_path_query
+------------------
+ "1"
+(1 row)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+ jsonb_path_query
+------------------
+ 1
+ "2"
+(2 rows)
+
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+ jsonb_path_query
+------------------
+ {"a": {"b": 1}}
+ {"b": 1}
+ 1
+(3 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+ jsonb_path_query
+------------------
+ {"b": 1}
+ 1
+(2 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+ jsonb_path_query
+------------------
+ 1
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"b": 1}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1}.b ? ( @ > 0)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{0 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to last}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{1 to 2}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": {"c": {"b": 1}}}' @? '$.**{2 to 3}.b ? ( @ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+ jsonb_path_query
+------------------
+ {"x": 2}
+(1 row)
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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)
+
+select jsonb '{"a": 1, "b":1}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$ ? (@.a == @.b)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.c ? ($.c.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.* ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"a": 1, "b":1}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 1, "b":1}}' @? '$.** ? (@.a == @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (@.b + 1))');
+ jsonb_path_query
+------------------
+ {"a": 2, "b": 1}
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - 1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -1)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == -@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": -1, "b":1}}' @? '$.** ? (@.a == - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 2, "b":1}}' @? '$.** ? (@.a == 1 - - @.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '{"c": {"a": 0, "b":1}}' @? '$.** ? (@.a == 1 - +@.b)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (+@[*] > +3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -2)';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '[1,2,3]' @? '$ ? (-@[*] < -3)';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '1' @? '$ ? ($ > 0)';
+ ?column?
+----------
+ t
+(1 row)
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+ jsonb_path_query
+------------------
+ 1
+ 2
+ 3
+(3 rows)
+
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+ jsonb_path_query
+------------------
+ 0
+(1 row)
+
+select jsonb_path_query('0', '1 / $');
+ERROR: division by zero
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+ jsonb_path_query
+------------------
+ 6
+(1 row)
+
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+ jsonb_path_query
+------------------
+ 5
+(1 row)
+
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+ jsonb_path_query
+------------------
+ -2
+ -3
+ -4
+(3 rows)
+
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+ERROR: Singleton SQL/JSON item required
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('2', '$ <= 1');
+ jsonb_path_query
+------------------
+ false
+(1 row)
+
+select jsonb_path_query('2', '$ == "2"');
+ jsonb_path_query
+------------------
+ null
+(1 row)
+
+select jsonb '2' @@ '$ > 1';
+ ?column?
+----------
+ t
+(1 row)
+
+select jsonb '2' @@ '$ <= 1';
+ ?column?
+----------
+ f
+(1 row)
+
+select jsonb '2' @@ '$ == "2"';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '2' @@ '1';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '{}' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[]' @@ '$';
+ ?column?
+----------
+
+(1 row)
+
+select jsonb '[1,2,3]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb '[]' @@ '$[*]';
+ERROR: Singleton SQL/JSON item required
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+ jsonb_path_match
+------------------
+ f
+(1 row)
+
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+ jsonb_path_match
+------------------
+ t
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+ jsonb_path_query
+------------------
+ "array"
+(1 row)
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+ jsonb_path_query
+------------------
+ "null"
+ "number"
+ "boolean"
+ "string"
+ "array"
+ "object"
+(6 rows)
+
+select jsonb_path_query('null', 'null.type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('null', 'true.type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('null', '123.type()');
+ jsonb_path_query
+------------------
+ "number"
+(1 row)
+
+select jsonb_path_query('null', '"123".type()');
+ jsonb_path_query
+------------------
+ "string"
+(1 row)
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+ jsonb_path_query
+------------------
+ 13
+(1 row)
+
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+ jsonb_path_query
+------------------
+ 4
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+ jsonb_path_query
+------------------
+ true
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "boolean"
+(1 row)
+
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+ jsonb_path_query
+------------------
+ "null"
+(1 row)
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+ERROR: SQL/JSON array not found
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+ jsonb_path_query
+------------------
+ 1
+ 1
+ 1
+ 1
+ 0
+ 1
+ 3
+ 1
+ 1
+(9 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3.4
+ 5.6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -4
+ 5
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ -2
+ -3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+ jsonb_path_query
+------------------
+ 0
+ 1
+ 2
+ 3
+ 6
+(5 rows)
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+ jsonb_path_query
+------------------
+ "number"
+ "number"
+ "number"
+ "number"
+ "number"
+(5 rows)
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('{}', '$.keyvalue()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+ jsonb_path_query
+----------------------------------------------
+ {"id": 0, "key": "a", "value": 1}
+ {"id": 0, "key": "b", "value": [1, 2]}
+ {"id": 0, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+ERROR: SQL/JSON object not found
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+ jsonb_path_query
+-----------------------------------------------
+ {"id": 12, "key": "a", "value": 1}
+ {"id": 12, "key": "b", "value": [1, 2]}
+ {"id": 72, "key": "c", "value": {"a": "bbb"}}
+(3 rows)
+
+select jsonb_path_query('null', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('true', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('[]', '$.double()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('{}', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('1.23', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23"', '$.double()');
+ jsonb_path_query
+------------------
+ 1.23
+(1 row)
+
+select jsonb_path_query('"1.23aaa"', '$.double()');
+ERROR: Non-numeric SQL/JSON item
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abcabc"
+(2 rows)
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ ["", "a", "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ ["abc", "abcabc", null, 1]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abc", "abcabc"]
+(1 row)
+
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+ jsonb_path_query
+----------------------------
+ [null, 1, "abd", "abdabc"]
+(1 row)
+
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+ jsonb_path_query
+------------------
+ null
+ 1
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+ jsonb_path_query
+------------------
+ "abc"
+ "abdacb"
+(2 rows)
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+ jsonb_path_query
+------------------
+ "abc"
+ "aBdC"
+ "abdacb"
+(3 rows)
+
+select jsonb_path_query('null', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('true', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('1', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('[]', '$.datetime()');
+ jsonb_path_query
+------------------
+(0 rows)
+
+select jsonb_path_query('[]', 'strict $.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('{}', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('""', '$.datetime()');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+set time zone '+00';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+00:12"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+ jsonb_path_query
+--------------------------------
+ "2017-03-10T12:34:00-00:12:34"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+ jsonb_path_query
+------------------
+ "12:34:00+00:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone '+10';
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00-05:20"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+ jsonb_path_query
+------------------
+ "12:34:00"
+(1 row)
+
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+ERROR: Invalid argument for SQL/JSON datetime function
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+ jsonb_path_query
+------------------
+ "12:34:00+10:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:00"
+(1 row)
+
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00+05:20"
+(1 row)
+
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+ jsonb_path_query
+------------------
+ "12:34:00-05:20"
+(1 row)
+
+set time zone default;
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+ jsonb_path_query
+------------------
+ "date"
+(1 row)
+
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "2017-03-10"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+-------------------------------
+ "timestamp without time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+ jsonb_path_query
+-----------------------
+ "2017-03-10T12:34:56"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+----------------------------
+ "timestamp with time zone"
+(1 row)
+
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:56+03:10"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+ jsonb_path_query
+--------------------------
+ "time without time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:00"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+ jsonb_path_query
+-----------------------
+ "time with time zone"
+(1 row)
+
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+ jsonb_path_query
+------------------
+ "12:34:56+03:10"
+(1 row)
+
+set time zone '+00';
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-10T00:00:00"
+ "2017-03-10T03:00:00+03:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10"
+ "2017-03-11"
+ "2017-03-10T00:00:00"
+ "2017-03-10T12:34:56"
+ "2017-03-10T03:00:00+03:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-09"
+ "2017-03-10T01:02:03+04:00"
+(2 rows)
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:35:00+00:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:35:00"
+ "12:36:00"
+ "12:35:00+00:00"
+(3 rows)
+
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+ jsonb_path_query
+------------------
+ "12:34:00"
+ "12:35:00+01:00"
+ "13:35:00+01:00"
+(3 rows)
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+(1 row)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:35:00+01:00"
+ "12:36:00+01:00"
+ "12:35:00-02:00"
+ "11:35:00"
+ "12:35:00"
+(5 rows)
+
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+ jsonb_path_query
+------------------
+ "12:34:00+01:00"
+ "12:35:00+02:00"
+ "10:35:00"
+(3 rows)
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T13:35:00+01:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00"
+ "2017-03-10T12:36:00"
+ "2017-03-10T13:35:00+01:00"
+ "2017-03-10T12:35:00-01:00"
+ "2017-03-11"
+(5 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00"
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10"
+(3 rows)
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T11:35:00"
+(2 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:35:00+01:00"
+ "2017-03-10T12:36:00+01:00"
+ "2017-03-10T12:35:00-02:00"
+ "2017-03-10T11:35:00"
+ "2017-03-10T12:35:00"
+ "2017-03-11"
+(6 rows)
+
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+ jsonb_path_query
+-----------------------------
+ "2017-03-10T12:34:00+01:00"
+ "2017-03-10T12:35:00+02:00"
+ "2017-03-10T10:35:00"
+ "2017-03-10"
+(4 rows)
+
+set time zone default;
+-- jsonpath operators
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+ jsonb_path_query
+------------------
+ {"a": 1}
+ {"a": 2}
+(2 rows)
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+ jsonb_path_query
+------------------
+(0 rows)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_array
+------------------------
+ [1, 2]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_array
+------------------------
+ [1]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ [2, 3]
+(1 row)
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_array
+------------------------
+ []
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+ jsonb_path_query_first
+------------------------
+ 1
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+ jsonb_path_query_first
+------------------------
+
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_query_first
+------------------------
+ 2
+(1 row)
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_query_first
+------------------------
+
+(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)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+ jsonb_path_exists
+-------------------
+ t
+(1 row)
+
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+ jsonb_path_exists
+-------------------
+ 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/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
new file mode 100644
index 0000000..a2143fd
--- /dev/null
+++ b/src/test/regress/expected/jsonpath.out
@@ -0,0 +1,788 @@
+--jsonpath io
+select ''::jsonpath;
+ERROR: invalid input syntax for jsonpath: ""
+LINE 1: select ''::jsonpath;
+ ^
+select '$'::jsonpath;
+ jsonpath
+----------
+ $
+(1 row)
+
+select 'strict $'::jsonpath;
+ jsonpath
+----------
+ strict $
+(1 row)
+
+select 'lax $'::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 '$.a[*]'::jsonpath;
+ jsonpath
+----------
+ $."a"[*]
+(1 row)
+
+select '$.a[*][*]'::jsonpath;
+ 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 '$.a.**.b'::jsonpath;
+ jsonpath
+--------------
+ $."a".**."b"
+(1 row)
+
+select '$.a.**{2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 2}.b'::jsonpath;
+ jsonpath
+-----------------
+ $."a".**{2}."b"
+(1 row)
+
+select '$.a.**{2 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{2 to 5}."b"
+(1 row)
+
+select '$.a.**{0 to 5}.b'::jsonpath;
+ jsonpath
+----------------------
+ $."a".**{0 to 5}."b"
+(1 row)
+
+select '$.a.**{5 to last}.b'::jsonpath;
+ jsonpath
+-------------------------
+ $."a".**{5 to last}."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 '"\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
+--------------------
+ $."g"?($."a" == 1)
+(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 || @.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 ? (@.x >= @[*]?(@.a > "abc"))'::jsonpath;
+ 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 ? (exists (@.x))'::jsonpath;
+ jsonpath
+------------------------
+ $."g"?(exists (@."x"))
+(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 ? (+@.x >= +-(+@.a + 2))'::jsonpath;
+ jsonpath
+------------------------------------
+ $."g"?(+@."x" >= +(-(+@."a" + 2)))
+(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
+---------------------------
+ $."g"?(@."zip" == $"zip")
+(1 row)
+
+select '$.a[1,2, 3 to 16]'::jsonpath;
+ 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 '$.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;
+ ^
+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 '$[$[0] ? (last > 0)]'::jsonpath;
+ jsonpath
+--------------------
+ $[$[0]?(last > 0)]
+(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 'true.type()'::jsonpath;
+ 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
+-------------------------
+ $?(@ starts with "abc")
+(1 row)
+
+select '$ ? (@ starts with $var)'::jsonpath;
+ 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 '$ < 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
+-----------
+ $."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 '1 + ($.a.b > 2).c.d'::jsonpath;
+ 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
+---------------
+ $?(@."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 cc0bbf5..42e0c23 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 0c10c71..46433c8 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 0000000..c50fd13
--- /dev/null
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -0,0 +1,395 @@
+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_path_query('[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_path_query('{"a": 12, "b": {"a": 13}}', '$.a');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.b');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', '$.*');
+select jsonb_path_query('{"a": 12, "b": {"a": 13}}', 'lax $.*.a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[*].*');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[2].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0,1].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}]', 'lax $[0 to 10].a');
+select jsonb_path_query('[12, {"a": 13}, {"b": 14}, "ccc", true]', '$[2.5 - 1 to $.size() - 2]');
+select jsonb_path_query('1', 'lax $[0]');
+select jsonb_path_query('1', 'lax $[*]');
+select jsonb_path_query('[1]', 'lax $[0]');
+select jsonb_path_query('[1]', 'lax $[*]');
+select jsonb_path_query('[1,2,3]', 'lax $[*]');
+select jsonb_path_query('[]', '$[last]');
+select jsonb_path_query('[]', 'strict $[last]');
+select jsonb_path_query('[1]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last]');
+select jsonb_path_query('[1,2,3]', '$[last - 1]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "number")]');
+select jsonb_path_query('[1,2,3]', '$[last ? (@.type() == "string")]');
+
+select * from jsonb_path_query('{"a": 10}', '$');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 13}');
+select * from jsonb_path_query('{"a": 10}', '$ ? (@.a < $value)', '{"value" : 8}');
+select * from jsonb_path_query('{"a": 10}', '$.a ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[*] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0,1] ? (@ < $value)', '{"value" : 13}');
+select * from jsonb_path_query('[10,11,12,13,14,15]', '$[0 to 2] ? (@ < $value)', '{"value" : 15}');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == "1")');
+select * from jsonb_path_query('[1,"1",2,"2",null]', '$[*] ? (@ == $value)', '{"value" : "1"}');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ != null)');
+select * from jsonb_path_query('[1, "2", null]', '$[*] ? (@ == null)');
+
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{2 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{3 to last}');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"b": 1}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{0 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to last}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{1 to 2}.b ? (@ > 0)');
+select jsonb_path_query('{"a": {"c": {"b": 1}}}', 'lax $.**{2 to 3}.b ? (@ > 0)');
+
+select jsonb '{"a": {"b": 1}}' @? '$.**.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{0}.b ? ( @ > 0)';
+select jsonb '{"a": {"b": 1}}' @? '$.**{1}.b ? ( @ > 0)';
+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 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_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.y))');
+select jsonb_path_query('{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
+
+--test ternary logic
+select
+ x, y,
+ jsonb_path_query(
+ '[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,
+ jsonb_path_query(
+ '[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 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_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == 1 + 1)');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == (1 + 1))');
+select jsonb_path_query('{"c": {"a": 2, "b":1}}', '$.** ? (@.a == @.b + 1)');
+select jsonb_path_query('{"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)';
+
+-- arithmetic errors
+select jsonb_path_query('[1,2,0,3]', '$[*] ? (2 / @ > 0)');
+select jsonb_path_query('[1,2,0,3]', '$[*] ? ((2 / @ > 0) is unknown)');
+select jsonb_path_query('0', '1 / $');
+
+-- unwrapping of operator arguments in lax mode
+select jsonb_path_query('{"a": [2]}', 'lax $.a * 3');
+select jsonb_path_query('{"a": [2]}', 'lax $.a + 3');
+select jsonb_path_query('{"a": [2, 3, 4]}', 'lax -$.a');
+-- should fail
+select jsonb_path_query('{"a": [1, 2]}', 'lax $.a * 3');
+
+-- extension: boolean expressions
+select jsonb_path_query('2', '$ > 1');
+select jsonb_path_query('2', '$ <= 1');
+select jsonb_path_query('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 jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] > $x) [1]', '{"x": 1}');
+select jsonb_path_match('[[1, true], [2, false]]', 'strict $[*] ? (@[0] < $x) [1]', '{"x": 2}');
+
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', 'lax $.type()');
+select jsonb_path_query('[null,1,true,"a",[],{}]', '$[*].type()');
+select jsonb_path_query('null', 'null.type()');
+select jsonb_path_query('null', 'true.type()');
+select jsonb_path_query('null', '123.type()');
+select jsonb_path_query('null', '"123".type()');
+
+select jsonb_path_query('{"a": 2}', '($.a - 5).abs() + 10');
+select jsonb_path_query('{"a": 2.5}', '-($.a * $.a).floor() + 10');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 2) ? (@ == true)');
+select jsonb_path_query('[1, 2, 3]', '($[*] > 3).type()');
+select jsonb_path_query('[1, 2, 3]', '($[*].a > 3).type()');
+select jsonb_path_query('[1, 2, 3]', 'strict ($[*].a > 3).type()');
+
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
+select jsonb_path_query('[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');
+
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].floor()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs()');
+select jsonb_path_query('[0, 1, -2, -3.4, 5.6]', '$[*].ceiling().abs().type()');
+
+select jsonb_path_query('[{},1]', '$[*].keyvalue()');
+select jsonb_path_query('{}', '$.keyvalue()');
+select jsonb_path_query('{"a": 1, "b": [1, 2], "c": {"a": "bbb"}}', '$.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', '$[*].keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'strict $.keyvalue()');
+select jsonb_path_query('[{"a": 1, "b": [1, 2]}, {"c": {"a": "bbb"}}]', 'lax $.keyvalue()');
+
+select jsonb_path_query('null', '$.double()');
+select jsonb_path_query('true', '$.double()');
+select jsonb_path_query('[]', '$.double()');
+select jsonb_path_query('[]', 'strict $.double()');
+select jsonb_path_query('{}', '$.double()');
+select jsonb_path_query('1.23', '$.double()');
+select jsonb_path_query('"1.23"', '$.double()');
+select jsonb_path_query('"1.23aaa"', '$.double()');
+
+select jsonb_path_query('["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
+select jsonb_path_query('["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
+select jsonb_path_query('["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
+select jsonb_path_query('[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
+select jsonb_path_query('[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
+
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c")');
+select jsonb_path_query('[null, 1, "abc", "abd", "aBdC", "abdacb", "babc"]', 'lax $[*] ? (@ like_regex "^ab.*c" flag "i")');
+
+select jsonb_path_query('null', '$.datetime()');
+select jsonb_path_query('true', '$.datetime()');
+select jsonb_path_query('1', '$.datetime()');
+select jsonb_path_query('[]', '$.datetime()');
+select jsonb_path_query('[]', 'strict $.datetime()');
+select jsonb_path_query('{}', '$.datetime()');
+select jsonb_path_query('""', '$.datetime()');
+
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017"', '$.datetime("dd-mm-yyyy").type()');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy").type()');
+
+select jsonb_path_query('"10-03-2017 12:34"', ' $.datetime("dd-mm-yyyy HH24:MI").type()');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM").type()');
+select jsonb_path_query('"12:34:56"', '$.datetime("HH24:MI:SS").type()');
+select jsonb_path_query('"12:34:56 +05:20"', '$.datetime("HH24:MI:SS TZH:TZM").type()');
+
+set time zone '+00';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+00:12")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "-00:12:34")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "UTC")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+00")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone '+10';
+
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34"', '$.datetime("dd-mm-yyyy HH24:MI TZH", "+10")');
+select jsonb_path_query('"10-03-2017 12:34 +05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 -05"', '$.datetime("dd-mm-yyyy HH24:MI TZH")');
+select jsonb_path_query('"10-03-2017 12:34 +05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"10-03-2017 12:34 -05:20"', '$.datetime("dd-mm-yyyy HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34"', '$.datetime("HH24:MI TZH", "+10")');
+select jsonb_path_query('"12:34 +05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 -05"', '$.datetime("HH24:MI TZH")');
+select jsonb_path_query('"12:34 +05:20"', '$.datetime("HH24:MI TZH:TZM")');
+select jsonb_path_query('"12:34 -05:20"', '$.datetime("HH24:MI TZH:TZM")');
+
+set time zone default;
+
+select jsonb_path_query('"2017-03-10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"2017-03-10 12:34:56 +3:10"', '$.datetime()');
+select jsonb_path_query('"12:34:56"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3"', '$.datetime()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime().type()');
+select jsonb_path_query('"12:34:56 +3:10"', '$.datetime()');
+
+set time zone '+00';
+
+-- date comparison
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ == "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ >= "10.03.2017".datetime("dd.mm.yyyy"))');
+select jsonb_path_query(
+ '["2017-03-10", "2017-03-11", "2017-03-09", "12:34:56", "01:02:03 +04", "2017-03-10 00:00:00", "2017-03-10 12:34:56", "2017-03-10 01:02:03 +04", "2017-03-10 03:00:00 +03"]',
+ '$[*].datetime() ? (@ < "10.03.2017".datetime("dd.mm.yyyy"))');
+
+-- time comparison
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ == "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ >= "12:35".datetime("HH24:MI"))');
+select jsonb_path_query(
+ '["12:34:00", "12:35:00", "12:36:00", "12:35:00 +00", "12:35:00 +01", "13:35:00 +01", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +01"]',
+ '$[*].datetime() ? (@ < "12:35".datetime("HH24:MI"))');
+
+-- timetz comparison
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ == "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ >= "12:35 +1".datetime("HH24:MI TZH"))');
+select jsonb_path_query(
+ '["12:34:00 +01", "12:35:00 +01", "12:36:00 +01", "12:35:00 +02", "12:35:00 -02", "10:35:00", "11:35:00", "12:35:00", "2017-03-10", "2017-03-10 12:35:00", "2017-03-10 12:35:00 +1"]',
+ '$[*].datetime() ? (@ < "12:35 +1".datetime("HH24:MI TZH"))');
+
+-- timestamp comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00", "2017-03-10 12:35:00", "2017-03-10 12:36:00", "2017-03-10 12:35:00 +01", "2017-03-10 13:35:00 +01", "2017-03-10 12:35:00 -01", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35".datetime("dd.mm.yyyy HH24:MI"))');
+
+-- timestamptz comparison
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ == "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ >= "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+select jsonb_path_query(
+ '["2017-03-10 12:34:00 +01", "2017-03-10 12:35:00 +01", "2017-03-10 12:36:00 +01", "2017-03-10 12:35:00 +02", "2017-03-10 12:35:00 -02", "2017-03-10 10:35:00", "2017-03-10 11:35:00", "2017-03-10 12:35:00", "2017-03-10", "2017-03-11", "12:34:56", "12:34:56 +01"]',
+ '$[*].datetime() ? (@ < "10.03.2017 12:35 +1".datetime("dd.mm.yyyy HH24:MI TZH"))');
+
+set time zone default;
+
+-- jsonpath operators
+
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*]');
+SELECT jsonb_path_query('[{"a": 1}, {"a": 2}]', '$[*] ? (@.a > 10)');
+
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_array('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ == 1)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}]', '$[*].a ? (@ > 10)');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_query_first('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*].a ? (@ > $min && @ < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*].a ? (@ > 1)';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @? '$[*] ? (@.a > 2)';
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 1, "max": 4}');
+SELECT jsonb_path_exists('[{"a": 1}, {"a": 2}, {"a": 3}, {"a": 5}]', '$[*] ? (@.a > $min && @.a < $max)', '{"min": 3, "max": 4}');
+
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 1';
+SELECT jsonb '[{"a": 1}, {"a": 2}]' @@ '$[*].a > 2';
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
new file mode 100644
index 0000000..b548bc8
--- /dev/null
+++ b/src/test/regress/sql/jsonpath.sql
@@ -0,0 +1,144 @@
+--jsonpath io
+
+select ''::jsonpath;
+select '$'::jsonpath;
+select 'strict $'::jsonpath;
+select 'lax $'::jsonpath;
+select '$.a'::jsonpath;
+select '$.a.v'::jsonpath;
+select '$.a.*'::jsonpath;
+select '$.*[*]'::jsonpath;
+select '$.a[*]'::jsonpath;
+select '$.a[*][*]'::jsonpath;
+select '$[*]'::jsonpath;
+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 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;
+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;
+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 ? (exists (@.x))'::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;
+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.size() - 3]'::jsonpath;
+select 'last'::jsonpath;
+select '"last"'::jsonpath;
+select '$.last'::jsonpath;
+select '$ ? (last > 0)'::jsonpath;
+select '$[last]'::jsonpath;
+select '$[$[0] ? (last > 0)]'::jsonpath;
+
+select 'null.type()'::jsonpath;
+select '1.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;
+
+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 '$ < 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 '($)'::jsonpath;
+select '(($))'::jsonpath;
+select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::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 56192f1..0738c37 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 e4bb9d2..c46bae7 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -327,6 +327,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',
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 84640c4..555fc25 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1095,14 +1095,31 @@ JoinType
JsObject
JsValue
JsonAggState
+JsonBaseObjectInfo
JsonHashEntry
+JsonItemStack
+JsonItemStackEntry
JsonIterateStringValuesAction
JsonLexContext
+JsonLikeRegexContext
JsonParseContext
+JsonPath
+JsonPathBool
+JsonPathExecContext
+JsonPathExecResult
+JsonPathItem
+JsonPathItemType
+JsonPathParseItem
+JsonPathParseResult
+JsonPathPredicateCallback
+JsonPathVariable
+JsonPathVariable_cb
JsonSemAction
JsonTokenType
JsonTransformStringValuesAction
JsonTypeCategory
+JsonValueList
+JsonValueListIterator
Jsonb
JsonbAggState
JsonbContainer
--
2.7.4
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-26 01:27 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-26 01:27 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Wed, Jan 23, 2019 at 8:01 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> Finally, I'm going to commit this if no objections.
BTW, I decided to postpone commit for few days. Nikita and me are
still working on better error messages.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-28 13:50 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2019-01-28 13:50 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 1/28/19 1:22 AM, Alexander Korotkov wrote:
> On Sun, Jan 27, 2019 at 1:50 PM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
>> On Sat, Jan 26, 2019 at 4:27 AM Alexander Korotkov
>> <a.korotkov@postgrespro.ru> wrote:
>>>
>>> On Wed, Jan 23, 2019 at 8:01 AM Alexander Korotkov
>>> <a.korotkov@postgrespro.ru> wrote:
>>>> Finally, I'm going to commit this if no objections.
>>>
>>> BTW, I decided to postpone commit for few days. Nikita and me are
>>> still working on better error messages.
>>
>> Updated patchset is attached. This patchset includes:
>>
>> * Improved error handling by Nikita, revised by me,
>> * Code beautification.
>>
>> So, I'm going to commit this again. This time seriously :)
>
> I'm really sorry for confusing people, but I've one more revision.
> This is my first time attempting to commit such a large patch.
>
I'm not sure what you're apologizing for, it simply shows how careful
you are when polishing such a large patch.
> Major changes are following:
> * We find it ridiculous to save ErrorData for possible latter throw.
> Now, we either throw an error immediately or return jperError. That
> also allows to get rid of unwanted changes in elog.c/elog.h.
OK.
> * I decided to change behavior of jsonb_path_match() to throw as less
> errors as possible. The reason is that it's used to implement
> potentially (patch is pending) indexable operator. Index scan is not
> always capable to throw as many errors and sequential scan. So, it's
> better to not introduce extra possible index scan and sequential scan
> results divergence.
>
Hmmm, can you elaborate a bit more? Which errors were thrown before and
are not thrown with the current patch version?
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-28 23:18 Nikita Glukhov <n.gluhov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Nikita Glukhov @ 2019-01-28 23:18 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 28.01.2019 16:50, Tomas Vondra wrote:
> On 1/28/19 1:22 AM, Alexander Korotkov wrote:
>> * I decided to change behavior of jsonb_path_match() to throw as less
>> errors as possible. The reason is that it's used to implement
>> potentially (patch is pending) indexable operator. Index scan is not
>> always capable to throw as many errors and sequential scan. So, it's
>> better to not introduce extra possible index scan and sequential scan
>> results divergence.
> Hmmm, can you elaborate a bit more? Which errors were thrown before and
> are not thrown with the current patch version?
In the previous version of the patch jsonb_path_match() threw error when
jsonpath did not return a singleton value, but in the last version in such cases
NULL is returned. This problem arises because we cannot guarantee at compile
time that jsonpath expression used in jsonb_path_match() is a predicate.
Predicates by standard can return only True, False, and Unknown (errors occurred
during execution of their operands are transformed into Unknown values), so
predicates cannot throw errors, and there are no problems with errors.
GIN does not attempt to search non-predicate expressions, so there may be no
problem even we throw "not a singleton" error.
Here I want to remind that ability to use predicates in the root of jsonpath
expression is an our extension to standard that was created specially for the
operator @@. By standard predicates are allowed only in filters. Without this
extension we are still able to rewrite @@ using @?:
jsonb @@ 'predicate' is equivalent to
jsonb @? '$ ? (predicate)'
but such @? expression is a bit slower to execute and a bit verbose to write.
If we introduced special type 'jsonpath_predicate', then we could solve the
problem by checking the type of jsonpath expression at compile-time.
Another problem with error handling is that jsonb_path_query*() functions
always throw SQL/JSON errors and there is no easy and effective way to emulate
NULL ON ERROR behavior, which is used by default in SQL/JSON functions. So I
think it's worth trying to add some kind of flag 'throwErrors' to
jsonb_path_query*() functions.
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:03 Andres Freund <andres@anarazel.de>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Andres Freund @ 2019-01-29 01:03 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> + /*
> + * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
> + * no function called inside performs data modification.
> + */
> + PG_TRY();
> + {
> + res = DirectFunctionCall2(func, ldatum, rdatum);
> + }
> + PG_CATCH();
> + {
> + int errcode = geterrcode();
> +
> + if (jspThrowErrors(cxt) ||
> + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> + PG_RE_THROW();
> +
> + MemoryContextSwitchTo(mcxt);
> + FlushErrorState();
> +
> + return jperError;
> + }
> + PG_END_TRY();
FWIW, I still think this is a terrible idea and shouldn't be merged this
way. The likelihood of introducing subtle bugs seems way too high - even
if it's possibly not buggy today, who says that it's not going to be in
the future?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:17 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Andres Freund <andres@anarazel.de>
1 sibling, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-29 01:17 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
> On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > + /*
> > + * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
> > + * no function called inside performs data modification.
> > + */
> > + PG_TRY();
> > + {
> > + res = DirectFunctionCall2(func, ldatum, rdatum);
> > + }
> > + PG_CATCH();
> > + {
> > + int errcode = geterrcode();
> > +
> > + if (jspThrowErrors(cxt) ||
> > + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> > + PG_RE_THROW();
> > +
> > + MemoryContextSwitchTo(mcxt);
> > + FlushErrorState();
> > +
> > + return jperError;
> > + }
> > + PG_END_TRY();
>
> FWIW, I still think this is a terrible idea and shouldn't be merged this
> way. The likelihood of introducing subtle bugs seems way too high - even
> if it's possibly not buggy today, who says that it's not going to be in
> the future?
I'm probably not yet understanding all the risks this code have. So far I see:
1) One of functions called here performs database modification, while
it wasn't suppose to. So, it becomes not safe to skip subtransaction.
2) ERRCODE_DATA_EXCEPTION was thrown for unexpected reason. So, it
might appear that ERRCODE_DATA_EXCEPTION is not safe to ignore.
Could you complete this list?
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:30 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Andres Freund <andres@anarazel.de>
1 sibling, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-29 01:30 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
> On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > + /*
> > + * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
> > + * no function called inside performs data modification.
> > + */
> > + PG_TRY();
> > + {
> > + res = DirectFunctionCall2(func, ldatum, rdatum);
> > + }
> > + PG_CATCH();
> > + {
> > + int errcode = geterrcode();
> > +
> > + if (jspThrowErrors(cxt) ||
> > + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> > + PG_RE_THROW();
> > +
> > + MemoryContextSwitchTo(mcxt);
> > + FlushErrorState();
> > +
> > + return jperError;
> > + }
> > + PG_END_TRY();
BTW, this code also looks... useless. I can't see whole numeric.c
throwing ERRCODE_DATA_EXCEPTION. Nikita, what do we mean here?
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:31 Andres Freund <andres@anarazel.de>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Andres Freund @ 2019-01-29 01:31 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 2019-01-29 04:17:33 +0300, Alexander Korotkov wrote:
> On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
> > On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > > + /*
> > > + * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
> > > + * no function called inside performs data modification.
> > > + */
> > > + PG_TRY();
> > > + {
> > > + res = DirectFunctionCall2(func, ldatum, rdatum);
> > > + }
> > > + PG_CATCH();
> > > + {
> > > + int errcode = geterrcode();
> > > +
> > > + if (jspThrowErrors(cxt) ||
> > > + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> > > + PG_RE_THROW();
> > > +
> > > + MemoryContextSwitchTo(mcxt);
> > > + FlushErrorState();
> > > +
> > > + return jperError;
> > > + }
> > > + PG_END_TRY();
> >
> > FWIW, I still think this is a terrible idea and shouldn't be merged this
> > way. The likelihood of introducing subtle bugs seems way too high - even
> > if it's possibly not buggy today, who says that it's not going to be in
> > the future?
>
> I'm probably not yet understanding all the risks this code have. So far I see:
I find these *more* than sufficient to not go to the PG_TRY/CATCH
approach.
> 1) One of functions called here performs database modification, while
> it wasn't suppose to. So, it becomes not safe to skip subtransaction.
It's not just data modifications. Even just modifying some memory
structures that'd normally be invalidated by an xact abort's
invalidation processing isn't safe.
> 2) ERRCODE_DATA_EXCEPTION was thrown for unexpected reason. So, it
> might appear that ERRCODE_DATA_EXCEPTION is not safe to ignore.
It'd e.g. not surprise me very much if some OOM would end up translating
to ERRCODE_DATA_EXCEPTION, because some library function returned an
error due to ENOMEM.
> Could you complete this list?
3) The expression changed the current expression context, GUCs or any
other such global variable. Without a proper subtrans reset this
state isn't reverted.
4) The function acquires an LWLOCK, buffer reference, anything resowner
owned. Skipping subtrans reset, that's not released in that
moment. That's going to lead to potential hard deadlocks.
99) sigsetjmp is actually pretty expensive.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:44 Tom Lane <tgl@sss.pgh.pa.us>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Tom Lane @ 2019-01-29 01:44 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Alexander Korotkov <a.korotkov@postgrespro.ru> writes:
> On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
>> FWIW, I still think this is a terrible idea and shouldn't be merged this
>> way. The likelihood of introducing subtle bugs seems way too high - even
>> if it's possibly not buggy today, who says that it's not going to be in
>> the future?
> I'm probably not yet understanding all the risks this code have. So far I see:
> 1) One of functions called here performs database modification, while
> it wasn't suppose to. So, it becomes not safe to skip subtransaction.
> 2) ERRCODE_DATA_EXCEPTION was thrown for unexpected reason. So, it
> might appear that ERRCODE_DATA_EXCEPTION is not safe to ignore.
> Could you complete this list?
Sure: every errcode we have is unsafe to treat this way.
The backend coding rule from day one has been that a thrown error requires
(sub)transaction cleanup to be done to make sure that things are back in a
good state. You can *not* just decide that it's okay to ignore that,
especially not when invoking code outside the immediate area of what
you're doing.
As a counterexample, for any specific errcode you might claim is safe,
a plpgsql function might do something that requires cleanup (which is
not equal to "does data modification") and then throw that errcode.
You might argue that this code will only ever be used to call certain
functions in numeric.c and you've vetted every line of code that those
functions can reach ... but even to say that is to expose how fragile
the argument is. Every time anybody touched numeric.c, or any code
reachable from there, we'd have to wonder whether we just introduced
a failure hazard for jsonpath. That's not maintainable. It's even
less maintainable if anybody decides they'd like to insert extension
hooks into any of that code. I rather imagine that this could be
broken today by an ErrorContextCallback function, for example.
(That is, even today, "vetting every line" has to include vetting every
errcontext subroutine in the system, along with everything it can call.
Plus equivalent code in external PLs and so on.)
We've rejected code like this every time it was submitted to date,
and I don't see a reason to change that policy for jsonpath.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:51 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-29 01:51 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 29, 2019 at 4:30 AM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
> > On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > > + /*
> > > + * It is safe to use here PG_TRY/PG_CATCH without subtransaction because
> > > + * no function called inside performs data modification.
> > > + */
> > > + PG_TRY();
> > > + {
> > > + res = DirectFunctionCall2(func, ldatum, rdatum);
> > > + }
> > > + PG_CATCH();
> > > + {
> > > + int errcode = geterrcode();
> > > +
> > > + if (jspThrowErrors(cxt) ||
> > > + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> > > + PG_RE_THROW();
> > > +
> > > + MemoryContextSwitchTo(mcxt);
> > > + FlushErrorState();
> > > +
> > > + return jperError;
> > > + }
> > > + PG_END_TRY();
>
> BTW, this code also looks... useless. I can't see whole numeric.c
> throwing ERRCODE_DATA_EXCEPTION. Nikita, what do we mean here?
Oh, sorry. I've missed we have ERRCODE_TO_CATEGORY() here.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:52 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-29 01:52 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Andres Freund <andres@anarazel.de>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 29, 2019 at 4:44 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Alexander Korotkov <a.korotkov@postgrespro.ru> writes:
> > On Tue, Jan 29, 2019 at 4:03 AM Andres Freund <andres@anarazel.de> wrote:
> >> FWIW, I still think this is a terrible idea and shouldn't be merged this
> >> way. The likelihood of introducing subtle bugs seems way too high - even
> >> if it's possibly not buggy today, who says that it's not going to be in
> >> the future?
>
> > I'm probably not yet understanding all the risks this code have. So far I see:
> > 1) One of functions called here performs database modification, while
> > it wasn't suppose to. So, it becomes not safe to skip subtransaction.
> > 2) ERRCODE_DATA_EXCEPTION was thrown for unexpected reason. So, it
> > might appear that ERRCODE_DATA_EXCEPTION is not safe to ignore.
> > Could you complete this list?
>
> Sure: every errcode we have is unsafe to treat this way.
>
> The backend coding rule from day one has been that a thrown error requires
> (sub)transaction cleanup to be done to make sure that things are back in a
> good state. You can *not* just decide that it's okay to ignore that,
> especially not when invoking code outside the immediate area of what
> you're doing.
>
> As a counterexample, for any specific errcode you might claim is safe,
> a plpgsql function might do something that requires cleanup (which is
> not equal to "does data modification") and then throw that errcode.
>
> You might argue that this code will only ever be used to call certain
> functions in numeric.c and you've vetted every line of code that those
> functions can reach ... but even to say that is to expose how fragile
> the argument is. Every time anybody touched numeric.c, or any code
> reachable from there, we'd have to wonder whether we just introduced
> a failure hazard for jsonpath. That's not maintainable. It's even
> less maintainable if anybody decides they'd like to insert extension
> hooks into any of that code. I rather imagine that this could be
> broken today by an ErrorContextCallback function, for example.
> (That is, even today, "vetting every line" has to include vetting every
> errcontext subroutine in the system, along with everything it can call.
> Plus equivalent code in external PLs and so on.)
>
> We've rejected code like this every time it was submitted to date,
> and I don't see a reason to change that policy for jsonpath.
Tom, Andres thank you for the explanation. More than clear for now.
Will search for workaround.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:53 Tom Lane <tgl@sss.pgh.pa.us>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 0 replies; 198+ messages in thread
From: Tom Lane @ 2019-01-29 01:53 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Alexander Korotkov <a.korotkov@postgrespro.ru> writes:
> + if (jspThrowErrors(cxt) ||
> + ERRCODE_TO_CATEGORY(errcode) != ERRCODE_DATA_EXCEPTION)
> + PG_RE_THROW();
> BTW, this code also looks... useless. I can't see whole numeric.c
> throwing ERRCODE_DATA_EXCEPTION. Nikita, what do we mean here?
I think what this code is claiming is that any errcode in the 22xxx
category is safe to trap. Presumably what it's expecting to see is
ERRCODE_DIVISION_BY_ZERO (22012) or another error that numeric.c
could throw ... but 22xxx covers a heck of a lot of ground.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 01:59 Andres Freund <andres@anarazel.de>
parent: Andres Freund <andres@anarazel.de>
0 siblings, 1 reply; 198+ messages in thread
From: Andres Freund @ 2019-01-29 01:59 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 2019-01-28 17:31:15 -0800, Andres Freund wrote:
> On 2019-01-29 04:17:33 +0300, Alexander Korotkov wrote:
> > I'm probably not yet understanding all the risks this code have. So far I see:
>
> I find these *more* than sufficient to not go to the PG_TRY/CATCH
> approach.
>
>
> > 1) One of functions called here performs database modification, while
> > it wasn't suppose to. So, it becomes not safe to skip subtransaction.
>
> It's not just data modifications. Even just modifying some memory
> structures that'd normally be invalidated by an xact abort's
> invalidation processing isn't safe.
>
>
> > 2) ERRCODE_DATA_EXCEPTION was thrown for unexpected reason. So, it
> > might appear that ERRCODE_DATA_EXCEPTION is not safe to ignore.
>
> It'd e.g. not surprise me very much if some OOM would end up translating
> to ERRCODE_DATA_EXCEPTION, because some library function returned an
> error due to ENOMEM.
>
>
> > Could you complete this list?
>
> 3) The expression changed the current expression context, GUCs or any
> other such global variable. Without a proper subtrans reset this
> state isn't reverted.
> 4) The function acquires an LWLOCK, buffer reference, anything resowner
> owned. Skipping subtrans reset, that's not released in that
> moment. That's going to lead to potential hard deadlocks.
> 99) sigsetjmp is actually pretty expensive.
And even if you, to address Tom's point about plpgsql, had a category
that could only be thrown by core code, and addressed 3) and 4) by
carefully and continuously auditing code, you'd still have the issue of
5) you'd likely leak memory at potentially prodiguous rate...
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-29 02:28 Tom Lane <tgl@sss.pgh.pa.us>
parent: Andres Freund <andres@anarazel.de>
0 siblings, 0 replies; 198+ messages in thread
From: Tom Lane @ 2019-01-29 02:28 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Andres Freund <andres@anarazel.de> writes:
> And even if you, to address Tom's point about plpgsql, had a category
> that could only be thrown by core code,
Actually, that wasn't quite what my point was there. Even if the error
that is thrown is perfectly safe and was thrown from a known spot in the
core code, elog.c will call ErrorContextCallback functions on the way out,
*before* longjmp'ing back to your code. So, if PL foo executes a SQL
command that includes jsonpath operations, it's possible for PL foo's
error context reporting code to break whatever assumptions you thought
were safe to make.
Admittedly, a wise PL designer would refrain from assuming too much about
what they can get away with doing in an error context callback. But
that very same caution should persuade us not to assume too much about
what actually has happened during error processing.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-30 02:28 Andres Freund <andres@anarazel.de>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Andres Freund @ 2019-01-30 02:28 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> +/*
> + * Make datetime type from 'date_txt' which is formated at argument 'fmt'.
> + * Actual datatype (returned in 'typid', 'typmod') is determined by
> + * presence of date/time/zone components in the format string.
> + */
> +Datum
> +to_datetime(text *date_txt, text *fmt, char *tzname, bool strict, Oid *typid,
> + int32 *typmod, int *tz)
Given other to_<type> functions being SQL callable, I'm not a fan of the
naming here.
> +{
> + struct pg_tm tm;
> + fsec_t fsec;
> + int fprec = 0;
> + int flags;
> +
> + do_to_timestamp(date_txt, fmt, strict, &tm, &fsec, &fprec, &flags);
> +
> + *typmod = fprec ? fprec : -1; /* fractional part precision */
> + *tz = 0;
> +
> + if (flags & DCH_DATED)
> + {
> + if (flags & DCH_TIMED)
> + {
> + if (flags & DCH_ZONED)
> + {
> + TimestampTz result;
> +
> + if (tm.tm_zone)
> + tzname = (char *) tm.tm_zone;
> +
> + if (tzname)
> + {
> + int dterr = DecodeTimezone(tzname, tz);
> +
> + if (dterr)
> + DateTimeParseError(dterr, tzname, "timestamptz");
Do we really need 6/7 indentation levels in new code?
> +jsonpath_scan.c: FLEXFLAGS = -CF -p -p
Why -CF, and why is -p repeated?
> - JsonEncodeDateTime(buf, val, DATEOID);
> + JsonEncodeDateTime(buf, val, DATEOID, NULL);
ISTM API changes like this ought to be done in a separate patch, to ease
review.
> }
>
> +
> /*
> * Compare two jbvString JsonbValue values, a and b.
> *
Random WS change.
> +/*****************************INPUT/OUTPUT*********************************/
Why do we need this much code to serialize data that we initially got in
serialized manner? Couldn't we just keep the original around?
> +Datum
> +jsonpath_in(PG_FUNCTION_ARGS)
> +{
No binary input support? I'd suggest adding that, but keeping the
representation the same. Otherwise just adds unnecesary complexity for
driver authors wishing to use the binar protocol.
> +/********************Support functions for JsonPath**************************/
> +
> +/*
> + * Support macroses to read stored values
> + */
s/macroses/macros/
> +++ b/src/backend/utils/adt/jsonpath_exec.c
Gotta say, I'm unenthusiastic about yet another execution engine in some
PG subsystem.
> @@ -0,0 +1,2776 @@
> +/*-------------------------------------------------------------------------
> + *
> + * jsonpath_exec.c
> + * Routines for SQL/JSON path execution.
> + *
> + * Copyright (c) 2019, PostgreSQL Global Development Group
> + *
> + * IDENTIFICATION
> + * src/backend/utils/adt/jsonpath_exec.c
> + *
> + *-------------------------------------------------------------------------
> + */
Somewhere there needs to be higher level documentation explaining how
this stuff is supposed to work on a code level.
> +
> +/* strict/lax flags is decomposed into four [un]wrap/error flags */
> +#define jspStrictAbsenseOfErrors(cxt) (!(cxt)->laxMode)
> +#define jspAutoUnwrap(cxt) ((cxt)->laxMode)
> +#define jspAutoWrap(cxt) ((cxt)->laxMode)
> +#define jspIgnoreStructuralErrors(cxt) ((cxt)->ignoreStructuralErrors)
> +#define jspThrowErrors(cxt) ((cxt)->throwErrors)
What's the point?
> +#define ThrowJsonPathError(code, detail) \
> + ereport(ERROR, \
> + (errcode(ERRCODE_ ## code), \
> + errmsg(ERRMSG_ ## code), \
> + errdetail detail))
> +
> +#define ThrowJsonPathErrorHint(code, detail, hint) \
> + ereport(ERROR, \
> + (errcode(ERRCODE_ ## code), \
> + errmsg(ERRMSG_ ## code), \
> + errdetail detail, \
> + errhint hint))
These seem ill-advised, just making it harder to understand the code,
and grepping for it, without actually meaningfully simplifying anything.
> +/*
> + * Find value of jsonpath variable in a list of passing params
> + */
What is that comment supposed to mean?
> +/*
> + * Get the type name of a SQL/JSON item.
> + */
> +static const char *
> +JsonbTypeName(JsonbValue *jb)
> +{
Wasn't there pretty similar infrastructure elsewhere?
> +/*
> + * Cross-type comparison of two datetime SQL/JSON items. If items are
> + * uncomparable, 'error' flag is set.
> + */
Sounds like it'd not raise an error, but it does in a bunch of scenarios.
> @@ -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
Are all of these invented by us?
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-30 04:34 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Andres Freund <andres@anarazel.de>
1 sibling, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-30 04:34 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi!
Thank you for your review! Let me answer some points of your review
while others will be answered later.
On Wed, Jan 30, 2019 at 5:28 AM Andres Freund <andres@anarazel.de> wrote:
> On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > +/*****************************INPUT/OUTPUT*********************************/
>
> Why do we need this much code to serialize data that we initially got in
> serialized manner? Couldn't we just keep the original around?
As I get, you concern related to fact that we have jsonpath in text
form (serialized) and convert it to the binary form (also serialized).
Yes, we should do so. Otherwise, we would have to parse jsonpath for
each evaluation. Basically, for the same reason we have binary
representation for jsonb.
> > +++ b/src/backend/utils/adt/jsonpath_exec.c
>
> Gotta say, I'm unenthusiastic about yet another execution engine in some
> PG subsystem.
Better ideas? I can imagine we can evade introduction of jsonpath
datatype and turn jsonpath into executor nodes. But I hardly can
imagine you would be more enthusiastic about that :)
> > @@ -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
>
> Are all of these invented by us?
None of them are invented by us. All of them are part of SQL 2016 standard.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-30 06:35 Andres Freund <andres@anarazel.de>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Andres Freund @ 2019-01-30 06:35 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 2019-01-30 07:34:00 +0300, Alexander Korotkov wrote:
> Thank you for your review! Let me answer some points of your review
> while others will be answered later.
>
> On Wed, Jan 30, 2019 at 5:28 AM Andres Freund <andres@anarazel.de> wrote:
> > On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > > +/*****************************INPUT/OUTPUT*********************************/
> >
> > Why do we need this much code to serialize data that we initially got in
> > serialized manner? Couldn't we just keep the original around?
>
> As I get, you concern related to fact that we have jsonpath in text
> form (serialized) and convert it to the binary form (also serialized).
> Yes, we should do so. Otherwise, we would have to parse jsonpath for
> each evaluation. Basically, for the same reason we have binary
> representation for jsonb.
But why can't we keep the text around, instead of having all of that
code for converting back?
> > > +++ b/src/backend/utils/adt/jsonpath_exec.c
> >
> > Gotta say, I'm unenthusiastic about yet another execution engine in some
> > PG subsystem.
>
> Better ideas? I can imagine we can evade introduction of jsonpath
> datatype and turn jsonpath into executor nodes. But I hardly can
> imagine you would be more enthusiastic about that :)
Not executor nodes, I think it could be possible to put it into the
expression evaluation codepaths, but it's probably too different to fit
well (would get you JIT compilation of the whole thing tho).
> > > @@ -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
> >
> > Are all of these invented by us?
>
> None of them are invented by us. All of them are part of SQL 2016 standard.
Cool.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-01-30 08:59 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Andres Freund <andres@anarazel.de>
0 siblings, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-01-30 08:59 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Wed, Jan 30, 2019 at 9:36 AM Andres Freund <andres@anarazel.de> wrote:
> On 2019-01-30 07:34:00 +0300, Alexander Korotkov wrote:
> > Thank you for your review! Let me answer some points of your review
> > while others will be answered later.
> >
> > On Wed, Jan 30, 2019 at 5:28 AM Andres Freund <andres@anarazel.de>
wrote:
> > > On 2019-01-29 04:00:19 +0300, Alexander Korotkov wrote:
> > > >
+/*****************************INPUT/OUTPUT*********************************/
> > >
> > > Why do we need this much code to serialize data that we initially got
in
> > > serialized manner? Couldn't we just keep the original around?
> >
> > As I get, you concern related to fact that we have jsonpath in text
> > form (serialized) and convert it to the binary form (also serialized).
> > Yes, we should do so. Otherwise, we would have to parse jsonpath for
> > each evaluation. Basically, for the same reason we have binary
> > representation for jsonb.
>
> But why can't we keep the text around, instead of having all of that
> code for converting back?
Yeah, that's possible. But now converting back to string is one of ways to
test that jsonpath parsing is correct. If we remove conversion back to
string, this possibility would be also removed. Also, we would loose way
to normalize jsonpath, which is probably not that important. As well it's
generally ugly redundancy. So, I can't say I like idea to save few
hundreds lines of codes for this price.
> > > > +++ b/src/backend/utils/adt/jsonpath_exec.c
> > >
> > > Gotta say, I'm unenthusiastic about yet another execution engine in
some
> > > PG subsystem.
> >
> > Better ideas? I can imagine we can evade introduction of jsonpath
> > datatype and turn jsonpath into executor nodes. But I hardly can
> > imagine you would be more enthusiastic about that :)
>
> Not executor nodes, I think it could be possible to put it into the
> expression evaluation codepaths, but it's probably too different to fit
> well (would get you JIT compilation of the whole thing tho).
Consider given example. We need to check some predicate for each JSON item,
where predicate is given by expression and set of items is produced by
another expression. In order to fit this into expression evaluation, we
probably need some kind of lamda functions there. It seems unlikely for me
that we want to implement that.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-02-04 04:57 Michael Paquier <michael@paquier.xyz>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 0 replies; 198+ messages in thread
From: Michael Paquier @ 2019-02-04 04:57 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Tue, Jan 29, 2019 at 04:51:46AM +0300, Alexander Korotkov wrote:
> Oh, sorry. I've missed we have ERRCODE_TO_CATEGORY() here.
Note: patch set moved to next CF, still waiting on author.
--
Michael
Attachments:
signature.asc (application/pgp-signature, 832B) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-02-24 11:44 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Tomas Vondra @ 2019-02-24 11:44 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Andres Freund <andres@anarazel.de>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
On 2/24/19 10:03 AM, Alexander Korotkov wrote:
> Hi!
>
> Attached is revised version of jsonpath. Assuming that jsonpath have
> problem places, I decided to propose partial implementation.
> Following functionality was cut from jsonpath:
> 1) Support of datetime datatypes. Besides error suppression, this
> functionality have troubles with timezones. According to standard we
> should support timezones in jsonpath expressions. But that would
> prevent jsonpath functions from being immutable, that in turn limits
> their usage in expression indexes.
Assuming we get the patch committed without the datetime stuff now, what
does that mean for the future? Does that mean we'll be unable to extend
it to support datetime in the future, or what? If we define the jsonpath
functions as immutable now, people may create indexes - which means we
won't be able to downgrade it to stable later.
So, what's the plan here? The only thing I can think of is having two
sets of functions - an immutable one, prohibiting datetime expressions,
and stable that can't be used for indexes etc.
> 2) Suppression of numeric errors. I will post it as a separate patch.
> Pushing this even this partial implementation of jsonpath to PG 12 is
> still very useful. Also it will simplify a lot pushing other parts of
> SQL/JSON to future releases.
>
+1 to push at least partial (but still useful) subset, instead of just
bumping the patch to PG13
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-02-24 12:34 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-02-24 12:34 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Andres Freund <andres@anarazel.de>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sun, Feb 24, 2019 at 2:44 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> On 2/24/19 10:03 AM, Alexander Korotkov wrote:
> > Attached is revised version of jsonpath. Assuming that jsonpath have
> > problem places, I decided to propose partial implementation.
> > Following functionality was cut from jsonpath:
> > 1) Support of datetime datatypes. Besides error suppression, this
> > functionality have troubles with timezones. According to standard we
> > should support timezones in jsonpath expressions. But that would
> > prevent jsonpath functions from being immutable, that in turn limits
> > their usage in expression indexes.
>
> Assuming we get the patch committed without the datetime stuff now, what
> does that mean for the future? Does that mean we'll be unable to extend
> it to support datetime in the future, or what? If we define the jsonpath
> functions as immutable now, people may create indexes - which means we
> won't be able to downgrade it to stable later.
>
> So, what's the plan here? The only thing I can think of is having two
> sets of functions - an immutable one, prohibiting datetime expressions,
> and stable that can't be used for indexes etc.
Reasonable question. As I understand, not datetime support itself
making jsonpath functions not immutable, but implicit cast happening
during comparison timestamp vs. timestamptz (and time vs. timetz).
So, in future immutable functions can have limited support of
datetime, where comparison of non-tz vs. tz types is restricted. And
stable versions of functions (for instance, with '_tz' prefix) with
full datetime support.
> > 2) Suppression of numeric errors. I will post it as a separate patch.
> > Pushing this even this partial implementation of jsonpath to PG 12 is
> > still very useful. Also it will simplify a lot pushing other parts of
> > SQL/JSON to future releases.
> >
>
> +1 to push at least partial (but still useful) subset, instead of just
> bumping the patch to PG13
Thank you for support!
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-01 00:49 Andres Freund <andres@anarazel.de>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
2 siblings, 0 replies; 198+ messages in thread
From: Andres Freund @ 2019-03-01 00:49 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 2019-03-01 03:36:49 +0300, Nikita Glukhov wrote:
> Patch 1 is what we are going to commit in PG12.
I think it's too early to make that determination. I think there's a
good chance, but that this needs more independent review.
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-01 05:40 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Nikita Glukhov <n.gluhov@postgrespro.ru>
2 siblings, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-03-01 05:40 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Fri, Mar 1, 2019 at 3:36 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> I can also offset to explicitly pass timezone info into jsonpath function using
> the special user dataype encapsulating struct pg_tz.
More interesting question is what would be the source of timezone. If
even you encapsulate timezone in a separate datatype, the expression
will be still just stable assuming timezone is generated by stable
subexpression. What we actually need is immutable timezone. Day once
timezone is updated, you create new timezone version, while old
version is immutable. Then if jsonpath has given particular *timezone
version*, it might remain immutable. But that requires significant
rework of our timezone infrastructure.
> But simple integer timezone offset can be passed now using jsonpath variables
> (standard says only about integer timezone offsets; also it requires presence
> of timezone offset it in the input string if the format string contain timezone
> components):
>
> =# SELECT jsonb_path_query(
> '"28-02-2019 12:34"',
> '$.datetime("DD-MM-YYYY HH24:MI TZH", $tz)',
> jsonb_build_object('tz', EXTRACT(TIMEZONE FROM now()))
> );
>
> jsonb_path_query
> -----------------------------
> "2019-02-28T12:34:00+03:00"
> (1 row)
Standard specifies fixed offset to be given for *particular datetime*.
For instance, if json contains offset in separate attribute or
whatever, then it's OK to use such two-arguments .datetime() method.
But that seems quite narrow use case.
Standard doesn't mean you get fixed offset extracted from "now()" and
apply it to random datetimes in your json collection. That would work
correctly for real timezones only when they are fixed offsets, but
there are almost none of them! So, that's just plain wrong, we never
should encourage users to do something like this.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-03 17:23 Pavel Stehule <pavel.stehule@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 0 replies; 198+ messages in thread
From: Pavel Stehule @ 2019-03-03 17:23 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>
so 2. 3. 2019 v 6:15 odesílatel Alexander Korotkov <
a.korotkov@postgrespro.ru> napsal:
> Hi!
>
> On Fri, Mar 1, 2019 at 3:36 AM Nikita Glukhov <n.gluhov@postgrespro.ru>
> wrote:
> >
> > Attached 34th version of the patches.
> >
> > 1. Partial jsonpath support:
> > - Fixed copying of jsonb with vars jsonb_path_query() into SRF context
> > - Fixed error message for jsonpath vars
> > - Fixed file-level comment in jsonpath.c
> >
> > 2. Suppression of numeric errors:
> > Now error handling is done without PG_TRY/PG_CATCH using a bunch of
> internal
> > numeric functions with 'bool *error' flag.
>
> Revised patches 1 and 2 are attached. Changes are following
>
> * Small refactoring, comments adjustment and function renaming. In
> particular, I've removed "recursive" prefix from function names,
> because it actually not that informative assuming header comment
> explains that the whole jsonpath executor is recursive. Also, I made
> "Unwrap" suffix more clear. Not it's distinguished what is unwrapped
> target (UnwrapTarget) or result (UnwrapResult). Also, now it's clear
> that function doesn't always unwraps but has an option to do so
> (OptUnwrap).
> * Some more cases are covered by regression tests.
>
These patches are large, but I think so the granularity and modularity of
these patches are correct.
Now, it looks very well.
Pavel
> ------
> Alexander Korotkov
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-03 18:08 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Tomas Vondra @ 2019-03-03 18:08 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Hi,
Here are some initial comments from a review of the 0001 part. I plan to
do more testing on a large data set and additional round of review over
the next week. FWIW I've passed this through valgrind and the usual
battery of regression tests, and there were no issues.
I haven't looked at 0002 yet, but it seems fairly small (especially
compared to 0001).
func.sgml
=========
1) I see the changes removed <indexterm zone="functions-json"> for some
reason. Is that intentional?
2) <command>WHERE</command>
We generally tag <literal>WHERE</literal>, not <command>.
3) Filter expressions are applied from left to right
Perhaps s/applied/evaluated/ in reference to expressions?
4) The result of the filter expression may be true, false, or unknown.
It's not entirely clear to me what "unknown" means here. NULL, or
something else? There's a section in the SQL/JSON standard
explaining this (page 83), but perhaps we should explain it a bit
here too?
The standard says "In the SQL/JSON data model, there are no SQL
nulls, so Unknown is not part of the SQL/JSON data model." so I'm a
bit confused what "unknown" references to. Maybe some example?
Also, what happens when the result is unknown?
5) There's an example showing how to apply filter at a certain level,
using the @ variable, but what if we want to apply multiple filters at
different levels? Would it make sense to add such example?
6) ... extensions of the SQL/JSON standard
I'm not sure what "extension" is here. Is that an extension defined
in the SQL standard, or an additional PostgreSQL functionality not
described in the standard? (I assume the latter, just checking.)
7) There are references to "SQL/JSON sequences" without any explanation
what it means. Maybe I'm missing something obvious, though.
8) Implicit unwrapping in the lax mode is not performed in the following
cases:
I suggest to reword it like this:
In the lax mode, implicit unwrapping is not performed when:
9) We're not adding the datetime() method for now, due to the issues
with timezones. I wonder if we should add a note why it's missing to the
docs ...
10) I'm a bit puzzled though, because the standard says this in the
description of type() function on page 77
If I is a datetime, then “date”, “time without time zone”, “time
with time zone”, “timestamp without time zone”, or “timestamp with
time zone”, as appropriate.
But I see our type() function does not return anything like that (which
I presume is independent of timezone stuff). But I see jsonb.h has no
concept of datetime values, and the standard actually says this in the
datetime() section
JSON has no datetime types. Datetime values are most likely stored
in character strings.
Considering this, the type() section makes little sense, no?
jsonb_util.c
============
I see we're now handling NaN values in convertJsonbScalar(). Isn't it
actually a bug that we don't do this already? Or is it not needed for
some reason?
jsonpath.c
==========
I suppose this should say "jsonpath version number" instead?
elog(ERROR, "unsupported jsonb version number %d", version);
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-04 01:37 Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Andrew Dunstan @ 2019-03-04 01:37 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On 3/3/19 1:08 PM, Tomas Vondra wrote:
>
>
> jsonb_util.c
> ============
>
> I see we're now handling NaN values in convertJsonbScalar(). Isn't it
> actually a bug that we don't do this already? Or is it not needed for
> some reason?
>
JSON standard numerics don't support NaN, Infinity etc., so I assume
this can only happen in a jsonpath expression being converted to a jsonb
value. If so, the new section should contain a comment to that effect,
otherwise it will be quite confusing.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-04 23:27 Tomas Vondra <tomas.vondra@2ndquadrant.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 2 replies; 198+ messages in thread
From: Tomas Vondra @ 2019-03-04 23:27 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
A bunch of additional comments, after looking at the patch a bit today.
All are mostly minor, and sometime perhaps a matter of preference.
1) There's a mismatch between the comment and actual function name for
jsonb_path_match_opr and jsonb_path_exists_opr(). The comments say
"_novars" instead.
2) In a couple of switches the "default" case does a return with a
value, following elog(ERROR). So it's practically unreachable, AFAICS
it's fine without it, and we don't do this elsewhere. And I don't get
any compiler warnings if I remove it either.
Examples:
JsonbTypeName
default:
elog(ERROR, "unrecognized jsonb value type: %d", jbv->type);
return "unknown";
jspOperationName
default:
elog(ERROR, "unrecognized jsonpath item type: %d", type);
return NULL;
compareItems
default:
elog(ERROR, "unrecognized jsonpath operation: %d", op);
return jpbUnknown;
3) jsonpath_send is using makeStringInfo() for a value that is not
returned - IMHO it should use regular stack-allocated variable and use
initStringInfo() instead
4) the version number should be defined/used as a constant, not as a
magic constant somewhere in the code
5) Why does jsonPathToCstring do this?
appendBinaryStringInfo(out, "strict ", 7);
Why not to use regular appendStringInfoString()? What am I missing?
6) comment typo: "Aling StringInfo"
7) alignStringInfoInt() should explain why we need this and why INTALIGN
is the right alignment.
8) I'm a bit puzzled by what flattenJsonPathParseItem does with 'next'
I don't quite understand what it's doing with 'next' value?
/*
* Actual value will be recorded later, after next and children
* processing.
*/
appendBinaryStringInfo(buf,
(char *) &next, /* fake value */
sizeof(next));
Perhaps a comment explaining it (why we need a fake value at all?) would
be a good idea here.
9) I see printJsonPathItem is only calling check_stack_depth while
flattenJsonPathParseItem also calls CHECK_INTERRUPTS. Why the
difference, considering they seem about equally expensive?
10) executeNumericItemMethod is missing a comment (unlike the other
executeXXX functions)
11) Wording of some of the error messages in the execute methods seems a
bit odd. For example executeNumericItemMethod may complain that it
... is applied to not a numeric value
but perhaps a more natural wording would be
... is applied to a non-numeric value
And similarly for the other execute methods. But I'm not a native
speaker, so perhaps the original wording is just fine.
regards
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-05 16:39 Robert Haas <robertmhaas@gmail.com>
parent: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 sibling, 0 replies; 198+ messages in thread
From: Robert Haas @ 2019-03-05 16:39 UTC (permalink / raw)
To: Tomas Vondra <tomas.vondra@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Mon, Mar 4, 2019 at 6:27 PM Tomas Vondra
<tomas.vondra@2ndquadrant.com> wrote:
> 11) Wording of some of the error messages in the execute methods seems a
> bit odd. For example executeNumericItemMethod may complain that it
>
> ... is applied to not a numeric value
>
> but perhaps a more natural wording would be
>
> ... is applied to a non-numeric value
>
> And similarly for the other execute methods. But I'm not a native
> speaker, so perhaps the original wording is just fine.
As a native speaker I can confirm that the first wording is definitely
not OK. The second one is tolerable, but I wonder if there is
something better, like "can only be applied to a numeric value" or
maybe there's a way to rephrase it so that we complain about the
non-numeric value itself rather than the application, e.g. ERROR:
json_frobnitz can only frob numeric values or ERROR: argument to
json_frobnitz must be numeric.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-14 09:07 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 1 reply; 198+ messages in thread
From: Alexander Korotkov @ 2019-03-14 09:07 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sun, Mar 10, 2019 at 1:51 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> On Wed, Mar 6, 2019 at 12:40 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> > Attached 36th version of the patches.
>
> Thank yo for the revision!
>
> In the attached revision following changes are made:
>
> > "unknown" refers here to ordinary three-valued logical Unknown, which is
> > represented in SQL by NULL.
> >
> > JSON path expressions return sequences of SQL/JSON items, which are defined by
> > SQL/JSON data model. But JSON path predicates (logical expressions), which are
> > used in filters, return three-valued logical values: False, True, or Unknown.
>
> * I've added short explanation of this to the documentation.
> * Removed no longer present data structures from typedefs.list of the
> first patch.
> * Moved GIN support patch to number 3. Seems to be well-isolated and
> not very complex patch. I propose to consider this to 12 too. I
> added high-level comment there, commit message and made some code
> beautification.
I think patches 1 and 2 are in committable shape (I reached Tomas
off-list, he doesn't have more notes regarding them). While patch 3
requires more review.
I'm going to push 1 and 2 if no objections.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 09:35 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
0 siblings, 2 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-03-16 09:35 UTC (permalink / raw)
To: Nikita Glukhov <n.gluhov@postgrespro.ru>; +Cc: Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Thu, Mar 14, 2019 at 12:07 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
> On Sun, Mar 10, 2019 at 1:51 PM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> > On Wed, Mar 6, 2019 at 12:40 AM Nikita Glukhov <n.gluhov@postgrespro.ru> wrote:
> > > Attached 36th version of the patches.
> >
> > Thank yo for the revision!
> >
> > In the attached revision following changes are made:
> >
> > > "unknown" refers here to ordinary three-valued logical Unknown, which is
> > > represented in SQL by NULL.
> > >
> > > JSON path expressions return sequences of SQL/JSON items, which are defined by
> > > SQL/JSON data model. But JSON path predicates (logical expressions), which are
> > > used in filters, return three-valued logical values: False, True, or Unknown.
> >
> > * I've added short explanation of this to the documentation.
> > * Removed no longer present data structures from typedefs.list of the
> > first patch.
> > * Moved GIN support patch to number 3. Seems to be well-isolated and
> > not very complex patch. I propose to consider this to 12 too. I
> > added high-level comment there, commit message and made some code
> > beautification.
>
> I think patches 1 and 2 are in committable shape (I reached Tomas
> off-list, he doesn't have more notes regarding them). While patch 3
> requires more review.
>
> I'm going to push 1 and 2 if no objections.
So, pushed. Many thanks to reviewers and authors!
Remaining part I'm proposing for 12 is attached. I appreciate review of it.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
0001-Jsonpath-GIN-support-v38.path (application/octet-stream, 50.0K) download
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 17:51 Jeff Janes <jeff.janes@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Jeff Janes @ 2019-03-16 17:51 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
On Sat, Mar 16, 2019 at 5:36 AM Alexander Korotkov <
a.korotkov@postgrespro.ru> wrote:
>
> So, pushed. Many thanks to reviewers and authors!
>
I think these files have to be cleaned up by "make maintainer-clean"
./src/backend/utils/adt/jsonpath_gram.c
./src/backend/utils/adt/jsonpath_scan.c
Cheers,
Jeff
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 17:55 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Jeff Janes <jeff.janes@gmail.com>
1 sibling, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-03-16 17:55 UTC (permalink / raw)
To: Jeff Janes <jeff.janes@gmail.com>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
сб, 16 мар. 2019 г., 20:52 Jeff Janes <jeff.janes@gmail.com>:
> On Sat, Mar 16, 2019 at 5:36 AM Alexander Korotkov <
> a.korotkov@postgrespro.ru> wrote:
>
>>
>> So, pushed. Many thanks to reviewers and authors!
>>
>
> I think these files have to be cleaned up by "make maintainer-clean"
>
> ./src/backend/utils/adt/jsonpath_gram.c
> ./src/backend/utils/adt/jsonpath_scan.c
>
Good catch, thanks! Will fix.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 17:59 Tom Lane <tgl@sss.pgh.pa.us>
parent: Jeff Janes <jeff.janes@gmail.com>
1 sibling, 1 reply; 198+ messages in thread
From: Tom Lane @ 2019-03-16 17:59 UTC (permalink / raw)
To: Jeff Janes <jeff.janes@gmail.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Jeff Janes <jeff.janes@gmail.com> writes:
> I think these files have to be cleaned up by "make maintainer-clean"
> ./src/backend/utils/adt/jsonpath_gram.c
> ./src/backend/utils/adt/jsonpath_scan.c
Good point. I also see jsonpath_gram.h left behind after maintainer-clean:
$ git status --ignored
On branch master
Your branch is up-to-date with 'origin/master'.
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
src/backend/utils/adt/jsonpath_gram.c
src/backend/utils/adt/jsonpath_scan.c
src/include/utils/jsonpath_gram.h
Looks like whoever modified src/backend/Makefile's distprep target
didn't bother to read the comment.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 18:11 Tom Lane <tgl@sss.pgh.pa.us>
parent: Tom Lane <tgl@sss.pgh.pa.us>
0 siblings, 2 replies; 198+ messages in thread
From: Tom Lane @ 2019-03-16 18:11 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Jeff Janes <jeff.janes@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
I wrote:
> Good point. I also see jsonpath_gram.h left behind after maintainer-clean:
Oh, and of potentially more significance: after maintainer-clean and
re-configure, make fails with
In file included from jsonpath_gram.y:24:
../../../../src/include/utils/jsonpath_scanner.h:25:33: error: utils/jsonpath_gram.h: No such file or directory
I first thought this was a problem with insufficient dependencies
allowing parallel make to do things in the wrong order, but the problem
repeats even without any parallelism. It looks like the dependencies
have been constructed in such a way that if the symlink at
src/include/utils/jsonpath_gram.h exists but the underlying file
does not, nothing will make the underlying file. This is pretty broken;
aside from this outright failure, it also suggests that nothing will
update that file if it exists but is out of date relative to its
sources.
Please make sure that the make rules associated with these files look
exactly like the previously-debugged rules for existing bison/flex
output files. There are generally good reasons for every last bit
of weirdness in those.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 18:16 Alexander Korotkov <a.korotkov@postgrespro.ru>
parent: Tom Lane <tgl@sss.pgh.pa.us>
1 sibling, 0 replies; 198+ messages in thread
From: Alexander Korotkov @ 2019-03-16 18:16 UTC (permalink / raw)
To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Jeff Janes <jeff.janes@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
сб, 16 мар. 2019 г., 21:12 Tom Lane <tgl@sss.pgh.pa.us>:
> I wrote:
> > Good point. I also see jsonpath_gram.h left behind after
> maintainer-clean:
>
> Oh, and of potentially more significance: after maintainer-clean and
> re-configure, make fails with
>
> In file included from jsonpath_gram.y:24:
> ../../../../src/include/utils/jsonpath_scanner.h:25:33: error:
> utils/jsonpath_gram.h: No such file or directory
>
> I first thought this was a problem with insufficient dependencies
> allowing parallel make to do things in the wrong order, but the problem
> repeats even without any parallelism. It looks like the dependencies
> have been constructed in such a way that if the symlink at
> src/include/utils/jsonpath_gram.h exists but the underlying file
> does not, nothing will make the underlying file. This is pretty broken;
> aside from this outright failure, it also suggests that nothing will
> update that file if it exists but is out of date relative to its
> sources.
>
> Please make sure that the make rules associated with these files look
> exactly like the previously-debugged rules for existing bison/flex
> output files. There are generally good reasons for every last bit
> of weirdness in those.
Uh, I didn't check that carefully enough. Thank you for the explanation.
Will fix.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-16 18:38 Pavel Stehule <pavel.stehule@gmail.com>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 1 reply; 198+ messages in thread
From: Pavel Stehule @ 2019-03-16 18:38 UTC (permalink / raw)
To: Alexander Korotkov <a.korotkov@postgrespro.ru>; +Cc: Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Tom Lane <tgl@sss.pgh.pa.us>; Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>
so 16. 3. 2019 v 10:36 odesílatel Alexander Korotkov <
a.korotkov@postgrespro.ru> napsal:
> On Thu, Mar 14, 2019 at 12:07 PM Alexander Korotkov
> <a.korotkov@postgrespro.ru> wrote:
> > On Sun, Mar 10, 2019 at 1:51 PM Alexander Korotkov
> > <a.korotkov@postgrespro.ru> wrote:
> > > On Wed, Mar 6, 2019 at 12:40 AM Nikita Glukhov <
> n.gluhov@postgrespro.ru> wrote:
> > > > Attached 36th version of the patches.
> > >
> > > Thank yo for the revision!
> > >
> > > In the attached revision following changes are made:
> > >
> > > > "unknown" refers here to ordinary three-valued logical Unknown,
> which is
> > > > represented in SQL by NULL.
> > > >
> > > > JSON path expressions return sequences of SQL/JSON items, which are
> defined by
> > > > SQL/JSON data model. But JSON path predicates (logical
> expressions), which are
> > > > used in filters, return three-valued logical values: False, True, or
> Unknown.
> > >
> > > * I've added short explanation of this to the documentation.
> > > * Removed no longer present data structures from typedefs.list of the
> > > first patch.
> > > * Moved GIN support patch to number 3. Seems to be well-isolated and
> > > not very complex patch. I propose to consider this to 12 too. I
> > > added high-level comment there, commit message and made some code
> > > beautification.
> >
> > I think patches 1 and 2 are in committable shape (I reached Tomas
> > off-list, he doesn't have more notes regarding them). While patch 3
> > requires more review.
> >
> > I'm going to push 1 and 2 if no objections.
>
> So, pushed. Many thanks to reviewers and authors!
>
> Remaining part I'm proposing for 12 is attached. I appreciate review of
> it.
>
I tested this patch and I didn't find any issue - just I tested basic
functionality and regress tests.
looks well
Pavel
>
> ------
> Alexander Korotkov
> Postgres Professional: http://www.postgrespro.com
> The Russian Postgres Company
>
^ permalink raw reply [nested|flat] 198+ messages in thread
* Re: jsonpath
@ 2019-03-17 15:03 Tom Lane <tgl@sss.pgh.pa.us>
parent: Alexander Korotkov <a.korotkov@postgrespro.ru>
1 sibling, 2 replies; 198+ messages in thread
From: Tom Lane @ 2019-03-17 15:03 UTC (permalink / raw)
To: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>; +Cc: Alexander Korotkov <a.korotkov@postgrespro.ru>; Jeff Janes <jeff.janes@gmail.com>; Nikita Glukhov <n.gluhov@postgrespro.ru>; Tomas Vondra <tomas.vondra@2ndquadrant.com>; Andres Freund <andres@anarazel.de>; Oleg Bartunov <obartunov@postgrespro.ru>; Michael Paquier <michael@paquier.xyz>; Stas Kelvich <s.kelvich@postgrespro.ru>; pgsql-hackers; Thomas Munro <thomas.munro@enterprisedb.com>; David Steele <david@pgmasters.net>; Robert Haas <robertmhaas@gmail.com>; Pavel Stehule <pavel.stehule@gmail.com>
Andrew Dunstan <andrew.dunstan@2ndquadrant.com> writes:
> Why are we installing the jsonpath_gram.h file? It's not going to be
> used by anything else, is it? TBH, I'm not sure I see why we're
> installing the scanner.h file either.
As near as I can see, jsonpath_gram.h and jsonpath_scanner.h exist only
for communication between jsonpath_gram.y and jsonpath_scan.l. Maybe
we'd be better off handling that need by compiling the .l file as part
of the .y file, as we used to do with the core lexer and still do with
several others (cf comments for commit 72b1e3a21); then we wouldn't
even have to generate these files much less install them.
A quick look at jsonpath_scan.c shows that it's pretty innocent of
the tricks we've learned over the years for flex/bison portability;
in particular I see that it's #including <stdio.h> before postgres.h,
which is a no-no. So that whole area needs more review anyway.
regards, tom lane
^ permalink raw reply [nested|flat] 198+ messages in thread
95 further messages in this thread omitted from this page (render size limit).
Use the per-message pages, or the t.mbox.gz byte stream, for the complete thread.
end of thread, other threads:[~2019-05-17 15:35 UTC | newest]
Thread overview: 198+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-01-07 19:31 jsonpath Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-07 20:00 ` Pavel Stehule <pavel.stehule@gmail.com>
2018-01-07 20:14 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-07 20:16 ` Pavel Stehule <pavel.stehule@gmail.com>
2018-01-07 20:20 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-07 20:23 ` Pavel Stehule <pavel.stehule@gmail.com>
2018-01-10 22:42 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-01-16 00:24 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-17 21:01 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-22 22:41 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-01-23 13:18 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-02-13 23:04 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-02-14 06:00 ` Oleg Bartunov <obartunov@gmail.com>
2018-02-26 15:34 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-02-26 19:36 ` Oleg Bartunov <obartunov@gmail.com>
2018-02-28 03:55 ` Robert Haas <robertmhaas@gmail.com>
2018-03-01 21:40 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-03-01 21:57 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2018-03-07 14:33 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-03-15 17:13 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-03-20 05:06 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-03-20 05:09 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2018-03-20 05:36 ` Tom Lane <tgl@sss.pgh.pa.us>
2018-03-27 15:13 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-04-10 13:32 ` David Steele <david@pgmasters.net>
2018-06-27 23:38 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-06-28 02:39 ` Thomas Munro <thomas.munro@enterprisedb.com>
2018-07-02 11:56 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-08-22 21:29 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-09-04 14:15 ` Stas Kelvich <s.kelvich@postgrespro.ru>
2018-09-07 23:21 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-10-02 02:33 ` Michael Paquier <michael@paquier.xyz>
2018-10-28 23:20 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-10-31 07:29 ` Oleg Bartunov <obartunov@postgrespro.ru>
2018-11-06 14:31 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-11-06 15:48 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-11-08 01:52 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-11-09 15:50 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-11-24 20:03 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-11-28 20:26 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2018-12-01 13:05 ` Dmitry Dolgov <9erthalion6@gmail.com>
2018-12-03 23:25 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-01-05 00:11 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-05 14:21 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-01-05 20:52 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-11 22:21 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-01-18 17:58 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-18 22:32 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-01-18 23:54 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-19 23:45 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-20 03:30 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-20 22:40 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-21 15:04 ` Oleg Bartunov <obartunov@postgrespro.ru>
2019-01-21 17:39 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-22 05:21 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-22 09:12 ` Oleg Bartunov <obartunov@postgrespro.ru>
2019-01-23 00:41 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-01-23 05:01 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-23 12:25 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-01-23 22:16 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-26 01:27 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-27 10:50 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-28 00:22 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-28 13:50 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-01-28 23:18 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-01-29 01:00 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-29 01:03 ` Andres Freund <andres@anarazel.de>
2019-01-29 01:17 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-29 01:31 ` Andres Freund <andres@anarazel.de>
2019-01-29 01:59 ` Andres Freund <andres@anarazel.de>
2019-01-29 02:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-01-29 01:44 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-01-29 01:52 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-29 01:30 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-29 01:51 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-02-04 04:57 ` Michael Paquier <michael@paquier.xyz>
2019-01-29 01:53 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-01-30 02:28 ` Andres Freund <andres@anarazel.de>
2019-01-30 04:34 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-30 06:35 ` Andres Freund <andres@anarazel.de>
2019-01-30 08:59 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-02-24 09:03 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-02-24 11:44 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-02-24 12:34 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-01 00:36 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-03-01 00:49 ` Andres Freund <andres@anarazel.de>
2019-03-01 05:40 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-02 05:15 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-03 17:23 ` Pavel Stehule <pavel.stehule@gmail.com>
2019-03-03 18:08 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-03-04 01:37 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-04 23:27 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-03-05 16:39 ` Robert Haas <robertmhaas@gmail.com>
2019-03-05 21:40 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-03-10 10:51 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-14 09:07 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-16 09:35 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-16 17:51 ` Jeff Janes <jeff.janes@gmail.com>
2019-03-16 17:55 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-16 17:59 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-16 18:11 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-16 18:16 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 08:03 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 14:00 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-17 15:03 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-17 16:31 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-19 17:10 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-21 13:58 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-22 00:15 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-03-22 03:46 ` Oleg Bartunov <obartunov@postgrespro.ru>
2019-03-22 02:38 ` John Naylor <john.naylor@2ndquadrant.com>
2019-03-22 12:14 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-24 16:44 ` Andres Freund <andres@anarazel.de>
2019-03-24 18:09 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-26 09:59 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-26 14:31 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-26 15:06 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-27 11:23 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-27 13:48 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-27 14:37 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-27 14:45 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-03-28 02:54 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-28 09:38 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-28 12:24 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-28 12:49 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-28 13:10 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-28 13:31 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 13:33 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-28 13:34 ` Andres Freund <andres@anarazel.de>
2019-03-28 13:50 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 16:43 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-29 13:15 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-30 15:25 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-03 20:02 ` Andres Freund <andres@anarazel.de>
2019-04-03 20:18 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-31 14:51 ` Tom Turelinckx <tom@turelinckx.be>
2019-03-31 15:18 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-31 16:21 ` Tom Turelinckx <tom@turelinckx.be>
2019-03-31 18:05 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-04-06 22:10 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-06 23:36 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-07 00:03 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-09 16:15 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2019-04-09 16:52 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-04-15 02:43 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-15 14:42 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-04-17 10:24 ` John Naylor <john.naylor@2ndquadrant.com>
2019-04-17 17:43 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-17 20:14 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-20 17:54 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-21 22:39 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-21 23:50 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-22 00:27 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-22 00:31 ` Alvaro Herrera <alvherre@2ndquadrant.com>
2019-04-24 18:03 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-25 18:03 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-25 19:28 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-28 18:28 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-29 15:10 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-04-29 22:20 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-05-07 04:14 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-05-07 14:35 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-05-07 22:06 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-05-17 03:50 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-05-17 15:35 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-18 01:08 ` John Naylor <john.naylor@2ndquadrant.com>
2019-04-20 18:01 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-04-22 00:07 ` John Naylor <john.naylor@2ndquadrant.com>
2019-04-12 16:09 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-04-12 16:21 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 05:01 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-28 11:45 ` Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
2019-03-22 11:48 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-03-16 18:38 ` Pavel Stehule <pavel.stehule@gmail.com>
2019-03-17 07:13 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 16:46 ` Jonathan S. Katz <jkatz@postgresql.org>
2019-03-17 16:55 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 17:00 ` Jonathan S. Katz <jkatz@postgresql.org>
2019-03-17 17:02 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 17:06 ` Jonathan S. Katz <jkatz@postgresql.org>
2019-03-17 17:14 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-17 18:29 ` Jonathan S. Katz <jkatz@postgresql.org>
2019-03-17 22:57 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2019-03-18 19:08 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-18 19:49 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-18 20:23 ` Tom Lane <tgl@sss.pgh.pa.us>
2019-03-18 20:24 ` Pavel Stehule <pavel.stehule@gmail.com>
2019-03-19 10:41 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-03-19 09:23 ` John Naylor <john.naylor@2ndquadrant.com>
2019-03-19 10:43 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-23 05:02 ` Alexander Korotkov <a.korotkov@postgrespro.ru>
2019-01-21 13:56 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
2018-11-08 01:57 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-11-08 05:12 ` Tom Lane <tgl@sss.pgh.pa.us>
2018-11-08 11:22 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-03-05 21:29 ` Tomas Vondra <tomas.vondra@2ndquadrant.com>
2018-03-05 22:56 ` Oleg Bartunov <obartunov@gmail.com>
2018-03-05 23:48 ` Nikita Glukhov <n.gluhov@postgrespro.ru>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox