Subject: [PATCH v1 1/8] Add jsonpath 'pg' modifier for enabling extensions

From: Nikita Glukhov <[email-masked]>
Date: Thu, 27 Jun 2019 19:58:35 +0300

---
 doc/src/sgml/func.sgml                 | 29 ++++++++++++++++++
 src/backend/utils/adt/jsonpath.c       | 54 ++++++++++++++++++++++++++--------
 src/backend/utils/adt/jsonpath_exec.c  |  4 +++
 src/backend/utils/adt/jsonpath_gram.y  | 17 +++++++----
 src/backend/utils/adt/jsonpath_scan.l  |  1 +
 src/include/utils/jsonpath.h           |  9 ++++--
 src/test/regress/expected/jsonpath.out | 18 ++++++++++++
 src/test/regress/sql/jsonpath.sql      |  3 ++
 src/tools/pgindent/typedefs.list       |  1 +
 9 files changed, 116 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 28035f1..d344b95 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -12601,6 +12601,35 @@ table2-mapping
 
    </sect3>
 
+   <sect3 id="pg-extensions">
+    <title>Extensions</title>
+    <para>
+     <productname>PostgreSQL</productname> has some extensions to the SQL/JSON
+     Path standard.  These syntax extensions that can enabled by the specifying
+     additional <literal>pg</literal> modifier before the
+     <literal>strict</literal>/<literal>lax</literal> flags.
+     <xref linkend="functions-jsonpath-extensions"/> shows these
+     extensions with examples.
+    </para>
+
+    <table id="functions-jsonpath-extensions">
+    <title><type>jsonpath</type> Syntax Extensions</title>
+     <tgroup cols="5">
+      <thead>
+       <row>
+        <entry>Name</entry>
+        <entry>Description</entry>
+        <entry>Example JSON</entry>
+        <entry>Example JSON path</entry>
+        <entry>Result</entry>
+       </row>
+      </thead>
+      <tbody>
+      </tbody>
+     </tgroup>
+    </table>
+   </sect3>
+
    <sect3 id="jsonpath-regular-expressions">
     <title>Regular Expressions</title>
 
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 3c0dc38..17c09a7 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -72,11 +72,20 @@
 #include "utils/jsonpath.h"

+/* Context for jsonpath encoding. */
+typedef struct JsonPathEncodingContext
+{
+	StringInfo	buf;		/* output buffer */
+	bool		ext;		/* PG extensions are enabled? */
+} JsonPathEncodingContext;
+
 static Datum jsonPathFromCstring(char *in, int len);
 static char *jsonPathToCstring(StringInfo out, JsonPath *in,
 							   int estimated_len);
-static int	flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
-									 int nestingLevel, bool insideArraySubscript);
+static int	flattenJsonPathParseItem(JsonPathEncodingContext *cxt,
+									 JsonPathParseItem *item,
+									 int nestingLevel,
+									 bool insideArraySubscript);
 static void alignStringInfoInt(StringInfo buf);
 static int32 reserveSpaceForItemPointer(StringInfo buf);
 static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
@@ -167,6 +176,7 @@ jsonpath_send(PG_FUNCTION_ARGS)
 static Datum
 jsonPathFromCstring(char *in, int len)
 {
+	JsonPathEncodingContext cxt;
 	JsonPathParseResult *jsonpath = parsejsonpath(in, len);
 	JsonPath   *res;
 	StringInfoData buf;
@@ -182,13 +192,18 @@ jsonPathFromCstring(char *in, int len)
 				 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
 						in)));
 
-	flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
+	cxt.buf = &buf;
+	cxt.ext = jsonpath->ext;
+
+	flattenJsonPathParseItem(&cxt, jsonpath->expr, 0, false);
 
 	res = (JsonPath *) buf.data;
 	SET_VARSIZE(res, buf.len);
 	res->header = JSONPATH_VERSION;
 	if (jsonpath->lax)
 		res->header |= JSONPATH_LAX;
+	if (jsonpath->ext)
+		res->header |= JSONPATH_EXT;
 
 	PG_RETURN_JSONPATH_P(res);
 }
@@ -212,6 +227,8 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
 	}
 	enlargeStringInfo(out, estimated_len);
 
+	if (in->header & JSONPATH_EXT)
+		appendBinaryStringInfo(out, "pg ", 3);
 	if (!(in->header & JSONPATH_LAX))
 		appendBinaryStringInfo(out, "strict ", 7);
 
@@ -221,14 +238,27 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
 	return out->data;
 }
 
+static void
+checkJsonPathExtensionsEnabled(JsonPathEncodingContext *cxt,
+							   JsonPathItemType type)
+{
+	if (!cxt->ext)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("%s contains extended operators that were not enabled", "jsonpath"),
+				 errhint("use \"%s\" modifier at the start of %s string to enable extensions",
+						 "pg", "jsonpath")));
+}
+
 /*
  * Recursive function converting given jsonpath parse item and all its
  * children into a binary representation.
  */
 static int
-flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
+flattenJsonPathParseItem(JsonPathEncodingContext *cxt, JsonPathParseItem *item,
 						 int nestingLevel, bool insideArraySubscript)
 {
+	StringInfo	buf = cxt->buf;
 	/* position from beginning of jsonpath data */
 	int32		pos = buf->len - JSONPATH_HDRSZ;
 	int32		chld;
@@ -296,13 +326,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 				int32		right = reserveSpaceForItemPointer(buf);
 
 				chld = !item->value.args.left ? pos :
-					flattenJsonPathParseItem(buf, item->value.args.left,
+					flattenJsonPathParseItem(cxt, item->value.args.left,
 											 nestingLevel + argNestingLevel,
 											 insideArraySubscript);
 				*(int32 *) (buf->data + left) = chld - pos;
 
 				chld = !item->value.args.right ? pos :
-					flattenJsonPathParseItem(buf, item->value.args.right,
+					flattenJsonPathParseItem(cxt, item->value.args.right,
 											 nestingLevel + argNestingLevel,
 											 insideArraySubscript);
 				*(int32 *) (buf->data + right) = chld - pos;
@@ -323,7 +353,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 									   item->value.like_regex.patternlen);
 				appendStringInfoChar(buf, '\0');
 
-				chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
+				chld = flattenJsonPathParseItem(cxt, item->value.like_regex.expr,
 												nestingLevel,
 												insideArraySubscript);
 				*(int32 *) (buf->data + offs) = chld - pos;
@@ -342,7 +372,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 				int32		arg = reserveSpaceForItemPointer(buf);
 
 				chld = !item->value.arg ? pos :
-					flattenJsonPathParseItem(buf, item->value.arg,
+					flattenJsonPathParseItem(cxt, item->value.arg,
 											 nestingLevel + argNestingLevel,
 											 insideArraySubscript);
 				*(int32 *) (buf->data + arg) = chld - pos;
@@ -384,12 +414,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 					int32	   *ppos;
 					int32		topos;
 					int32		frompos =
-					flattenJsonPathParseItem(buf,
+					flattenJsonPathParseItem(cxt,
 											 item->value.array.elems[i].from,
 											 nestingLevel, true) - pos;
 
 					if (item->value.array.elems[i].to)
-						topos = flattenJsonPathParseItem(buf,
+						topos = flattenJsonPathParseItem(cxt,
 														 item->value.array.elems[i].to,
 														 nestingLevel, true) - pos;
 					else
@@ -424,7 +454,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
 
 	if (item->next)
 	{
-		chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
+		chld = flattenJsonPathParseItem(cxt, item->next, nestingLevel,
 										insideArraySubscript) - pos;
 		*(int32 *) (buf->data + next) = chld;
 	}
@@ -832,7 +862,7 @@ operationPriority(JsonPathItemType op)
 void
 jspInit(JsonPathItem *v, JsonPath *js)
 {
-	Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
+	Assert((js->header & JSONPATH_VERSION_MASK) == JSONPATH_VERSION);
 	jspInitByBuffer(v, js->data, 0);
 }
 
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index b6fdd47..9f13f4b 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -101,6 +101,8 @@ typedef struct JsonPathExecContext
 	int			innermostArraySize; /* for LAST array index evaluation */
 	bool		laxMode;		/* true for "lax" mode, false for "strict"
 								 * mode */
+	bool		useExtensions;	/* use PostgreSQL-specific extensions?
+								 * (enabled by 'pg' modifier in jsonpath) */
 	bool		ignoreStructuralErrors; /* with "true" structural errors such
 										 * as absence of required json item or
 										 * unexpected json item type are
@@ -157,6 +159,7 @@ typedef struct JsonValueListIterator
 #define jspAutoWrap(cxt)				((cxt)->laxMode)
 #define jspIgnoreStructuralErrors(cxt)	((cxt)->ignoreStructuralErrors)
 #define jspThrowErrors(cxt)				((cxt)->throwErrors)
+#define jspUseExtensions(cxt)			((cxt)->useExtensions)
 
 /* Convenience macro: return or throw error depending on context */
 #define RETURN_ERROR(throw_error) \
@@ -559,6 +562,7 @@ executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
 
 	cxt.vars = vars;
 	cxt.laxMode = (path->header & JSONPATH_LAX) != 0;
+	cxt.useExtensions = (path->header & JSONPATH_EXT) != 0;
 	cxt.ignoreStructuralErrors = cxt.laxMode;
 	cxt.root = &jbv;
 	cxt.current = &jbv;
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index f87db8c..426dbb1 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -88,7 +88,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 	int					integer;
 }
 
-%token	<str>		TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
+%token	<str>		TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P PG_P
 %token	<str>		IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
 %token	<str>		OR_P AND_P NOT_P
 %token	<str>		LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
@@ -109,7 +109,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 
 %type	<optype>	comp_op method
 
-%type	<boolean>	mode
+%type	<boolean>	mode pg_opt
 
 %type	<str>		key_name
 
@@ -127,10 +127,11 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
 %%
 
 result:
-	mode expr_or_predicate			{
+	pg_opt mode expr_or_predicate	{
 										*result = palloc(sizeof(JsonPathParseResult));
-										(*result)->expr = $2;
-										(*result)->lax = $1;
+										(*result)->expr = $3;
+										(*result)->lax = $2;
+										(*result)->ext = $1;
 									}
 	| /* EMPTY */					{ *result = NULL; }
 	;
@@ -140,6 +141,11 @@ expr_or_predicate:
 	| predicate						{ $$ = $1; }
 	;
 
+pg_opt:
+	PG_P							{ $$ = true; }
+	| /* EMPTY */					{ $$ = false; }
+	;
+
 mode:
 	STRICT_P						{ $$ = false; }
 	| LAX_P							{ $$ = true; }
@@ -292,6 +298,7 @@ key_name:
 	| WITH_P
 	| LIKE_REGEX_P
 	| FLAG_P
+	| PG_P
 	;
 
 method:
diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l
index 70681b7..c8bce27 100644
--- a/src/backend/utils/adt/jsonpath_scan.l
+++ b/src/backend/utils/adt/jsonpath_scan.l
@@ -305,6 +305,7 @@ typedef struct JsonPathKeyword
  */
 static const JsonPathKeyword keywords[] = {
 	{ 2, false,	IS_P,		"is"},
+	{ 2, false,	PG_P,		"pg"},
 	{ 2, false,	TO_P,		"to"},
 	{ 3, false,	ABS_P,		"abs"},
 	{ 3, false,	LAX_P,		"lax"},
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 4ef0880..d56175f 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -25,8 +25,10 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } JsonPath;
 
-#define JSONPATH_VERSION	(0x01)
-#define JSONPATH_LAX		(0x80000000)
+#define JSONPATH_VERSION	0x01
+#define JSONPATH_LAX		0x80000000		/* lax/strict mode */
+#define JSONPATH_EXT		0x40000000		/* PG extensions */
+#define JSONPATH_VERSION_MASK (~(JSONPATH_LAX | JSONPATH_EXT))
 #define JSONPATH_HDRSZ		(offsetof(JsonPath, data))
 
 #define DatumGetJsonPathP(d)			((JsonPath *) DatumGetPointer(PG_DETOAST_DATUM(d)))
@@ -241,7 +243,8 @@ struct JsonPathParseItem
 typedef struct JsonPathParseResult
 {
 	JsonPathParseItem *expr;
-	bool		lax;
+	bool		lax;		/* lax/strict mode */
+	bool		ext;		/* PostgreSQL extensions */
 } JsonPathParseResult;
 
 extern JsonPathParseResult *parsejsonpath(const char *str, int len);
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index e399fa9..52b36a8 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -21,6 +21,24 @@ select 'lax $'::jsonpath;
  $
 (1 row)
 
+select 'pg $'::jsonpath;
+ jsonpath 
+----------
+ pg $
+(1 row)
+
+select 'pg strict $'::jsonpath;
+  jsonpath   
+-------------
+ pg strict $
+(1 row)
+
+select 'pg lax $'::jsonpath;
+ jsonpath 
+----------
+ pg $
+(1 row)
+
 select '$.a'::jsonpath;
  jsonpath 
 ----------
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 17ab775..315e42f 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -4,6 +4,9 @@ select ''::jsonpath;
 select '$'::jsonpath;
 select 'strict $'::jsonpath;
 select 'lax $'::jsonpath;
+select 'pg $'::jsonpath;
+select 'pg strict $'::jsonpath;
+select 'pg lax $'::jsonpath;
 select '$.a'::jsonpath;
 select '$.a.v'::jsonpath;
 select '$.a.*'::jsonpath;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index e216de9..d4148e1 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1130,6 +1130,7 @@ JsonLikeRegexContext
 JsonParseContext
 JsonPath
 JsonPathBool
+JsonPathEncodingContext
 JsonPathExecContext
 JsonPathExecResult
 JsonPathGinAddPathItemFunc
-- 
2.7.4

--------------574DCE16CAD28E71E2083DCD
Content-Type: text/x-patch;
 name="v1-0002-Add-raw-jbvArray-and-jbvObject-support-to-jsonpat.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="v1-0002-Add-raw-jbvArray-and-jbvObject-support-to-jsonpat.pa";
 filename*1="tch"