1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-30 11:03:19 +03:00

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
This commit is contained in:
Andrew Dunstan
2022-03-03 13:02:53 -05:00
parent 6198420ad8
commit 33a377608f
25 changed files with 856 additions and 17 deletions

View File

@ -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

View File

@ -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);
}