From: Nikita Glukhov Date: Thu, 27 Jun 2019 19:58:35 +0300 Subject: [PATCH v1 1/8] Add jsonpath 'pg' modifier for enabling extensions --- 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 + + Extensions + + PostgreSQL has some extensions to the SQL/JSON + Path standard. These syntax extensions that can enabled by the specifying + additional pg modifier before the + strict/lax flags. + shows these + extensions with examples. + + + + <type>jsonpath</type> Syntax Extensions + + + + Name + Description + Example JSON + Example JSON path + Result + + + + + +
+
+ Regular Expressions 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 TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P +%token TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P PG_P %token IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P %token OR_P AND_P NOT_P %token LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P @@ -109,7 +109,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr, %type comp_op method -%type mode +%type mode pg_opt %type 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"