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