aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
authorAndrew Dunstan <andrew@dunslane.net>2022-03-03 13:02:53 -0500
committerAndrew Dunstan <andrew@dunslane.net>2022-03-28 15:37:08 -0400
commit33a377608fc29cdd1f6b63be561eab0aee5c81f0 (patch)
tree47abccafdaeae1c4466c917bbd85912f39558360 /src/backend/parser
parent6198420ad8a72e37f4fe4964616b17e0fd33b808 (diff)
downloadpostgresql-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.y63
-rw-r--r--src/backend/parser/parse_expr.c76
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);
+}