public inbox for [email protected]help / color / mirror / Atom feed
[PATCH 1/5] sequential scan for dshash 9+ messages / 5 participants [nested] [flat]
* [PATCH 1/5] sequential scan for dshash @ 2018-06-29 07:41 Kyotaro Horiguchi <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Kyotaro Horiguchi @ 2018-06-29 07:41 UTC (permalink / raw) Add sequential scan feature to dshash. --- src/backend/lib/dshash.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++- src/include/lib/dshash.h | 23 +++++- 2 files changed, 206 insertions(+), 5 deletions(-) diff --git a/src/backend/lib/dshash.c b/src/backend/lib/dshash.c index f095196fb6..d1908a6137 100644 --- a/src/backend/lib/dshash.c +++ b/src/backend/lib/dshash.c @@ -112,6 +112,7 @@ struct dshash_table size_t size_log2; /* log2(number of buckets) */ bool find_locked; /* Is any partition lock held by 'find'? */ bool find_exclusively_locked; /* ... exclusively? */ + bool seqscan_running;/* now under sequential scan */ }; /* Given a pointer to an item, find the entry (user data) it holds. */ @@ -127,6 +128,10 @@ struct dshash_table #define NUM_SPLITS(size_log2) \ (size_log2 - DSHASH_NUM_PARTITIONS_LOG2) +/* How many buckets are there in a given size? */ +#define NUM_BUCKETS(size_log2) \ + (((size_t) 1) << (size_log2)) + /* How many buckets are there in each partition at a given size? */ #define BUCKETS_PER_PARTITION(size_log2) \ (((size_t) 1) << NUM_SPLITS(size_log2)) @@ -153,6 +158,10 @@ struct dshash_table #define BUCKET_INDEX_FOR_PARTITION(partition, size_log2) \ ((partition) << NUM_SPLITS(size_log2)) +/* Choose partition based on bucket index. */ +#define PARTITION_FOR_BUCKET_INDEX(bucket_idx, size_log2) \ + ((bucket_idx) >> NUM_SPLITS(size_log2)) + /* The head of the active bucket for a given hash value (lvalue). */ #define BUCKET_FOR_HASH(hash_table, hash) \ (hash_table->buckets[ \ @@ -228,6 +237,7 @@ dshash_create(dsa_area *area, const dshash_parameters *params, void *arg) hash_table->find_locked = false; hash_table->find_exclusively_locked = false; + hash_table->seqscan_running = false; /* * Set up the initial array of buckets. Our initial size is the same as @@ -279,6 +289,7 @@ dshash_attach(dsa_area *area, const dshash_parameters *params, hash_table->control = dsa_get_address(area, control); hash_table->find_locked = false; hash_table->find_exclusively_locked = false; + hash_table->seqscan_running = false; Assert(hash_table->control->magic == DSHASH_MAGIC); /* @@ -324,7 +335,7 @@ dshash_destroy(dshash_table *hash_table) ensure_valid_bucket_pointers(hash_table); /* Free all the entries. */ - size = ((size_t) 1) << hash_table->size_log2; + size = NUM_BUCKETS(hash_table->size_log2); for (i = 0; i < size; ++i) { dsa_pointer item_pointer = hash_table->buckets[i]; @@ -549,9 +560,14 @@ dshash_delete_entry(dshash_table *hash_table, void *entry) LW_EXCLUSIVE)); delete_item(hash_table, item); - hash_table->find_locked = false; - hash_table->find_exclusively_locked = false; - LWLockRelease(PARTITION_LOCK(hash_table, partition)); + + /* We need to keep partition lock while sequential scan */ + if (!hash_table->seqscan_running) + { + hash_table->find_locked = false; + hash_table->find_exclusively_locked = false; + LWLockRelease(PARTITION_LOCK(hash_table, partition)); + } } /* @@ -568,6 +584,8 @@ dshash_release_lock(dshash_table *hash_table, void *entry) Assert(LWLockHeldByMeInMode(PARTITION_LOCK(hash_table, partition_index), hash_table->find_exclusively_locked ? LW_EXCLUSIVE : LW_SHARED)); + /* lock is under control of sequential scan */ + Assert(!hash_table->seqscan_running); hash_table->find_locked = false; hash_table->find_exclusively_locked = false; @@ -592,6 +610,168 @@ dshash_memhash(const void *v, size_t size, void *arg) return tag_hash(v, size); } +/* + * dshash_seq_init/_next/_term + * Sequentially scan trhough dshash table and return all the + * elements one by one, return NULL when no more. + * + * dshash_seq_term should be called if and only if the scan is abandoned + * before completion; if dshash_seq_next returns NULL then it has already done + * the end-of-scan cleanup. + * + * On returning element, it is locked as is the case with dshash_find. + * However, the caller must not release the lock. The lock is released as + * necessary in continued scan. + * + * As opposed to the equivalent for dynanash, the caller is not supposed to + * delete the returned element before continuing the scan. + * + * If consistent is set for dshash_seq_init, the whole hash table is + * non-exclusively locked. Otherwise a part of the hash table is locked in the + * same mode (partition lock). + */ +void +dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table, + bool consistent, bool exclusive) +{ + /* allowed at most one scan at once */ + Assert(!hash_table->seqscan_running); + + status->hash_table = hash_table; + status->curbucket = 0; + status->nbuckets = 0; + status->curitem = NULL; + status->pnextitem = InvalidDsaPointer; + status->curpartition = -1; + status->consistent = consistent; + status->exclusive = exclusive; + hash_table->seqscan_running = true; + + /* + * Protect all partitions from modification if the caller wants a + * consistent result. + */ + if (consistent) + { + int i; + + for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i) + { + Assert(!LWLockHeldByMe(PARTITION_LOCK(hash_table, i))); + + LWLockAcquire(PARTITION_LOCK(hash_table, i), + exclusive ? LW_EXCLUSIVE : LW_SHARED); + } + ensure_valid_bucket_pointers(hash_table); + } +} + +void * +dshash_seq_next(dshash_seq_status *status) +{ + dsa_pointer next_item_pointer; + + Assert(status->hash_table->seqscan_running); + if (status->curitem == NULL) + { + int partition; + + Assert (status->curbucket == 0); + Assert(!status->hash_table->find_locked); + + /* first shot. grab the first item. */ + if (!status->consistent) + { + partition = + PARTITION_FOR_BUCKET_INDEX(status->curbucket, + status->hash_table->size_log2); + LWLockAcquire(PARTITION_LOCK(status->hash_table, partition), + status->exclusive ? LW_EXCLUSIVE : LW_SHARED); + status->curpartition = partition; + + /* resize doesn't happen from now until seq scan ends */ + status->nbuckets = + NUM_BUCKETS(status->hash_table->control->size_log2); + ensure_valid_bucket_pointers(status->hash_table); + } + + next_item_pointer = status->hash_table->buckets[status->curbucket]; + } + else + next_item_pointer = status->pnextitem; + + /* Move to the next bucket if we finished the current bucket */ + while (!DsaPointerIsValid(next_item_pointer)) + { + if (++status->curbucket >= status->nbuckets) + { + /* all buckets have been scanned. finsih. */ + dshash_seq_term(status); + return NULL; + } + + /* Also move parititon lock if needed */ + if (!status->consistent) + { + int next_partition = + PARTITION_FOR_BUCKET_INDEX(status->curbucket, + status->hash_table->size_log2); + + /* Move lock along with partition for the bucket */ + if (status->curpartition != next_partition) + { + /* + * Take lock on the next partition then release the current, + * not in the reverse order. This is required to avoid + * resizing from happening during a sequential scan. Locks are + * taken in partition order so no dead lock happen with other + * seq scans or resizing. + */ + LWLockAcquire(PARTITION_LOCK(status->hash_table, + next_partition), + status->exclusive ? LW_EXCLUSIVE : LW_SHARED); + LWLockRelease(PARTITION_LOCK(status->hash_table, + status->curpartition)); + status->curpartition = next_partition; + } + } + + next_item_pointer = status->hash_table->buckets[status->curbucket]; + } + + status->curitem = + dsa_get_address(status->hash_table->area, next_item_pointer); + status->hash_table->find_locked = true; + status->hash_table->find_exclusively_locked = status->exclusive; + + /* + * This item can be deleted by the caller. Store the next item for the + * next iteration for the occasion. + */ + status->pnextitem = status->curitem->next; + + return ENTRY_FROM_ITEM(status->curitem); +} + +void +dshash_seq_term(dshash_seq_status *status) +{ + Assert(status->hash_table->seqscan_running); + status->hash_table->find_locked = false; + status->hash_table->find_exclusively_locked = false; + status->hash_table->seqscan_running = false; + + if (status->consistent) + { + int i; + + for (i = 0; i < DSHASH_NUM_PARTITIONS; ++i) + LWLockRelease(PARTITION_LOCK(status->hash_table, i)); + } + else if (status->curpartition >= 0) + LWLockRelease(PARTITION_LOCK(status->hash_table, status->curpartition)); +} + /* * Print debugging information about the internal state of the hash table to * stderr. The caller must hold no partition locks. diff --git a/src/include/lib/dshash.h b/src/include/lib/dshash.h index e5dfd57f0a..b80f3af995 100644 --- a/src/include/lib/dshash.h +++ b/src/include/lib/dshash.h @@ -59,6 +59,23 @@ typedef struct dshash_parameters struct dshash_table_item; typedef struct dshash_table_item dshash_table_item; +/* + * Sequential scan state of dshash. The detail is exposed since the storage + * size should be known to users but it should be considered as an opaque + * type by callers. + */ +typedef struct dshash_seq_status +{ + dshash_table *hash_table; + int curbucket; + int nbuckets; + dshash_table_item *curitem; + dsa_pointer pnextitem; + int curpartition; + bool consistent; + bool exclusive; +} dshash_seq_status; + /* Creating, sharing and destroying from hash tables. */ extern dshash_table *dshash_create(dsa_area *area, const dshash_parameters *params, @@ -70,7 +87,6 @@ extern dshash_table *dshash_attach(dsa_area *area, extern void dshash_detach(dshash_table *hash_table); extern dshash_table_handle dshash_get_hash_table_handle(dshash_table *hash_table); extern void dshash_destroy(dshash_table *hash_table); - /* Finding, creating, deleting entries. */ extern void *dshash_find(dshash_table *hash_table, const void *key, bool exclusive); @@ -80,6 +96,11 @@ extern bool dshash_delete_key(dshash_table *hash_table, const void *key); extern void dshash_delete_entry(dshash_table *hash_table, void *entry); extern void dshash_release_lock(dshash_table *hash_table, void *entry); +/* seq scan support */ +extern void dshash_seq_init(dshash_seq_status *status, dshash_table *hash_table, + bool consistent, bool exclusive); +extern void *dshash_seq_next(dshash_seq_status *status); +extern void dshash_seq_term(dshash_seq_status *status); /* Convenience hash and compare functions wrapping memcmp and tag_hash. */ extern int dshash_memcmp(const void *a, const void *b, size_t size, void *arg); extern dshash_hash dshash_memhash(const void *v, size_t size, void *arg); -- 2.16.3 ----Next_Part(Mon_Feb_18_21_35_31_2019_949)-- Content-Type: Text/X-Patch; charset=us-ascii Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="v14-0002-Add-conditional-lock-feature-to-dshash.patch" ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH 3/4] Jsonpath syntax extensions @ 2018-09-07 22:27 Nikita Glukhov <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Nikita Glukhov @ 2018-09-07 22:27 UTC (permalink / raw) --- 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 --------------14FD26200DFA6FA5CAAC1774 Content-Type: text/x-patch; name="0004-Jsonpath-GIN-support-v18.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0004-Jsonpath-GIN-support-v18.patch" ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH 4/4] Jsonpath syntax extensions @ 2018-11-06 13:33 Nikita Glukhov <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Nikita Glukhov @ 2018-11-06 13:33 UTC (permalink / raw) --- 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 --------------65FB62A2E0811333017E53AC-- ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH 4/4] Jsonpath syntax extensions @ 2018-11-06 13:33 Nikita Glukhov <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Nikita Glukhov @ 2018-11-06 13:33 UTC (permalink / raw) --- 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 --------------9B31BF271468F658D603E21E-- ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH 06/13] Jsonpath syntax extensions @ 2018-12-03 23:05 Nikita Glukhov <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Nikita Glukhov @ 2018-12-03 23:05 UTC (permalink / raw) --- 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 --------------8408635FF00110718B05B39A-- ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH 06/13] Jsonpath syntax extensions @ 2019-01-11 22:11 Nikita Glukhov <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Nikita Glukhov @ 2019-01-11 22:11 UTC (permalink / raw) --- 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 --------------3DFBBE88C12891994AAFB68D-- ^ permalink raw reply [nested|flat] 9+ messages in thread
* [PATCH v4 01/12] Change section heading to better reflect saving a result in variable(s) @ 2023-09-24 20:49 Karl O. Pinc <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Karl O. Pinc @ 2023-09-24 20:49 UTC (permalink / raw) The current section title of "Executing a Command with a Single-Row Result" does not reflect what the section is really about. Other sections make clear how to _execute_ commands, single-row result or not. What this section is about is how to _save_ a single row of results into variable(s). It would be nice to talk about saving results into variables in the section heading but I couldn't come up with anything pithy. "Saving a Single-Row of a Command's Result" seems good enough, especially since there's few other places to save results other than in variables. --- doc/src/sgml/plpgsql.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index f55e901c7e..8747e84245 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1126,7 +1126,7 @@ PERFORM create_mv('cs_session_page_requests_mv', my_query); </sect2> <sect2 id="plpgsql-statements-sql-onerow"> - <title>Executing a Command with a Single-Row Result</title> + <title>Saving a Single-Row of a Command's Result</title> <indexterm zone="plpgsql-statements-sql-onerow"> <primary>SELECT INTO</primary> -- 2.30.2 --MP_/OOXZvOwbpccKfGOtE9/SwX6 Content-Type: text/x-patch Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=v4-0002-Change-section-heading-to-better-describe-referen.patch ^ permalink raw reply [nested|flat] 9+ messages in thread
* a potential typo in comments of pg_parse_json @ 2024-07-08 08:24 Junwang Zhao <[email protected]> 0 siblings, 1 reply; 9+ messages in thread From: Junwang Zhao @ 2024-07-08 08:24 UTC (permalink / raw) To: PostgreSQL Hackers <[email protected]> Not 100% sure, sorry if this doesn't make sense. --- a/src/common/jsonapi.c +++ b/src/common/jsonapi.c @@ -514,7 +514,7 @@ freeJsonLexContext(JsonLexContext *lex) * * If FORCE_JSON_PSTACK is defined then the routine will call the non-recursive * JSON parser. This is a useful way to validate that it's doing the right - * think at least for non-incremental cases. If this is on we expect to see + * thing at least for non-incremental cases. If this is on we expect to see * regression diffs relating to error messages about stack depth, but no * other differences. */ -- Regards Junwang Zhao ^ permalink raw reply [nested|flat] 9+ messages in thread
* Re: a potential typo in comments of pg_parse_json @ 2024-07-08 13:16 Amit Langote <[email protected]> parent: Junwang Zhao <[email protected]> 0 siblings, 0 replies; 9+ messages in thread From: Amit Langote @ 2024-07-08 13:16 UTC (permalink / raw) To: Junwang Zhao <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]> On Mon, Jul 8, 2024 at 5:25 PM Junwang Zhao <[email protected]> wrote: > Not 100% sure, sorry if this doesn't make sense. > > --- a/src/common/jsonapi.c > +++ b/src/common/jsonapi.c > @@ -514,7 +514,7 @@ freeJsonLexContext(JsonLexContext *lex) > * > * If FORCE_JSON_PSTACK is defined then the routine will call the non-recursive > * JSON parser. This is a useful way to validate that it's doing the right > - * think at least for non-incremental cases. If this is on we expect to see > + * thing at least for non-incremental cases. If this is on we expect to see > * regression diffs relating to error messages about stack depth, but no > * other differences. > */ Good catch. Fixed. -- Thanks, Amit Langote ^ permalink raw reply [nested|flat] 9+ messages in thread
end of thread, other threads:[~2024-07-08 13:16 UTC | newest] Thread overview: 9+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-06-29 07:41 [PATCH 1/5] sequential scan for dshash Kyotaro Horiguchi <[email protected]> 2018-09-07 22:27 [PATCH 3/4] Jsonpath syntax extensions Nikita Glukhov <[email protected]> 2018-11-06 13:33 [PATCH 4/4] Jsonpath syntax extensions Nikita Glukhov <[email protected]> 2018-11-06 13:33 [PATCH 4/4] Jsonpath syntax extensions Nikita Glukhov <[email protected]> 2018-12-03 23:05 [PATCH 06/13] Jsonpath syntax extensions Nikita Glukhov <[email protected]> 2019-01-11 22:11 [PATCH 06/13] Jsonpath syntax extensions Nikita Glukhov <[email protected]> 2023-09-24 20:49 [PATCH v4 01/12] Change section heading to better reflect saving a result in variable(s) Karl O. Pinc <[email protected]> 2024-07-08 08:24 a potential typo in comments of pg_parse_json Junwang Zhao <[email protected]> 2024-07-08 13:16 ` Re: a potential typo in comments of pg_parse_json Amit Langote <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox