diff options
author | Andrew Dunstan <andrew@dunslane.net> | 2022-03-03 13:02:53 -0500 |
---|---|---|
committer | Andrew Dunstan <andrew@dunslane.net> | 2022-03-28 15:37:08 -0400 |
commit | 33a377608fc29cdd1f6b63be561eab0aee5c81f0 (patch) | |
tree | 47abccafdaeae1c4466c917bbd85912f39558360 /src/backend/parser | |
parent | 6198420ad8a72e37f4fe4964616b17e0fd33b808 (diff) | |
download | postgresql-33a377608fc29cdd1f6b63be561eab0aee5c81f0.tar.gz postgresql-33a377608fc29cdd1f6b63be561eab0aee5c81f0.zip |
IS JSON predicate
This patch intrdocuces the SQL standard IS JSON predicate. It operates
on text and bytea values representing JSON as well as on the json and
jsonb types. Each test has an IS and IS NOT variant. The tests are:
IS JSON [VALUE]
IS JSON ARRAY
IS JSON OBJECT
IS JSON SCALAR
IS JSON WITH | WITHOUT UNIQUE KEYS
These are mostly self-explanatory, but note that IS JSON WITHOUT UNIQUE
KEYS is true whenever IS JSON is true, and IS JSON WITH UNIQUE KEYS is
true whenever IS JSON is true except it IS JSON OBJECT is true and there
are duplicate keys (which is never the case when applied to jsonb values).
Nikita Glukhov
Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu,
Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby.
Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/gram.y | 63 | ||||
-rw-r--r-- | src/backend/parser/parse_expr.c | 76 |
2 files changed, 134 insertions, 5 deletions
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 9399fff610f..b658bcc182c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -665,6 +665,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <ival> json_encoding json_encoding_clause_opt + json_predicate_type_constraint_opt %type <boolean> json_key_uniqueness_constraint_opt json_object_constructor_null_clause_opt @@ -734,7 +735,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_OBJECT JSON_OBJECTAGG - KEY KEYS + KEY KEYS KEEP LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL @@ -763,9 +764,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP ROUTINE ROUTINES ROW ROWS RULE - SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES - SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW - SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P + SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT + SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF + SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P @@ -853,13 +854,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); * Using the same precedence as IDENT seems right for the reasons given above. */ %nonassoc UNBOUNDED /* ideally would have same precedence as IDENT */ -%nonassoc ABSENT UNIQUE +%nonassoc ABSENT UNIQUE JSON %nonassoc IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP %left Op OPERATOR /* multi-character ops and user-defined operators */ %left '+' '-' %left '*' '/' '%' %left '^' %left KEYS /* UNIQUE [ KEYS ] */ +%left OBJECT_P SCALAR VALUE_P /* JSON [ OBJECT | SCALAR | VALUE ] */ /* Unary Operators */ %left AT /* sets precedence for AT TIME ZONE */ %left COLLATE @@ -14141,6 +14143,46 @@ a_expr: c_expr { $$ = $1; } @2), @2); } + | a_expr + IS json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + $$ = makeJsonIsPredicate($1, format, $3, $4, @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeJsonIsPredicate($1, $3, $5, $6, @1); + } + */ + | a_expr + IS NOT + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec IS + { + JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1); + $$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1); + } + /* + * Required by standard, but it would conflict with expressions + * like: 'str' || format(...) + | a_expr + FORMAT json_representation + IS NOT + json_predicate_type_constraint_opt + json_key_uniqueness_constraint_opt %prec FORMAT + { + $3.location = @2; + $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1); + } + */ | DEFAULT { /* @@ -14223,6 +14265,14 @@ b_expr: c_expr } ; +json_predicate_type_constraint_opt: + JSON { $$ = JS_TYPE_ANY; } + | JSON VALUE_P { $$ = JS_TYPE_ANY; } + | JSON ARRAY { $$ = JS_TYPE_ARRAY; } + | JSON OBJECT_P { $$ = JS_TYPE_OBJECT; } + | JSON SCALAR { $$ = JS_TYPE_SCALAR; } + ; + json_key_uniqueness_constraint_opt: WITH_LA_UNIQUE unique_keys { $$ = true; } | WITHOUT unique_keys { $$ = false; } @@ -16412,6 +16462,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL @@ -16882,6 +16933,7 @@ bare_label_keyword: | JSON_ARRAYAGG | JSON_OBJECT | JSON_OBJECTAGG + | KEEP | KEY | KEYS | LABEL @@ -17011,6 +17063,7 @@ bare_label_keyword: | ROWS | RULE | SAVEPOINT + | SCALAR | SCHEMA | SCHEMAS | SCROLL diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 84be354f714..0b972ea6322 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -85,6 +85,7 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate, JsonArrayQueryConstructor *ctor); static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg); static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg); +static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -332,6 +333,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr); break; + case T_JsonIsPredicate: + result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -3869,3 +3874,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor) returning, false, ctor->absent_on_null, ctor->location); } + +static Node * +transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format, + Oid *exprtype) +{ + Node *raw_expr = transformExprRecurse(pstate, jsexpr); + Node *expr = raw_expr; + + *exprtype = exprType(expr); + + /* prepare input document */ + if (*exprtype == BYTEAOID) + { + JsonValueExpr *jve; + + expr = makeCaseTestExpr(raw_expr); + expr = makeJsonByteaToTextConversion(expr, format, exprLocation(expr)); + *exprtype = TEXTOID; + + jve = makeJsonValueExpr((Expr *) raw_expr, format); + + jve->formatted_expr = (Expr *) expr; + expr = (Node *) jve; + } + else + { + char typcategory; + bool typispreferred; + + get_type_category_preferred(*exprtype, &typcategory, &typispreferred); + + if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING) + { + expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype, + TEXTOID, -1, + COERCION_IMPLICIT, + COERCE_IMPLICIT_CAST, -1); + *exprtype = TEXTOID; + } + + if (format->encoding != JS_ENC_DEFAULT) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + parser_errposition(pstate, format->location), + errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types"))); + } + + return expr; +} + +/* + * Transform IS JSON predicate into + * json[b]_is_valid(json, value_type [, check_key_uniqueness]) call. + */ +static Node * +transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred) +{ + Oid exprtype; + Node *expr = transformJsonParseArg(pstate, pred->expr, pred->format, + &exprtype); + + /* make resulting expression */ + if (exprtype != TEXTOID && exprtype != JSONOID && exprtype != JSONBOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot use type %s in IS JSON predicate", + format_type_be(exprtype)))); + + return makeJsonIsPredicate(expr, NULL, pred->value_type, + pred->unique_keys, pred->location); +} |