mirror of
https://github.com/postgres/postgres.git
synced 2025-07-31 22:04:40 +03:00
SQL/JSON: support the IS JSON predicate
This patch introduces 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 IS and IS NOT variants and supports a WITH UNIQUE KEYS flag. The tests are: IS JSON [VALUE] IS JSON ARRAY IS JSON OBJECT IS JSON SCALAR These should be self-explanatory. The WITH UNIQUE KEYS flag makes these return false when duplicate keys exist in any object within the value, not necessarily directly contained in the outermost object. Author: Nikita Glukhov <n.gluhov@postgrespro.ru> Author: Teodor Sigaev <teodor@sigaev.ru> Author: Oleg Bartunov <obartunov@gmail.com> Author: Alexander Korotkov <aekorotkov@gmail.com> Author: Amit Langote <amitlangote09@gmail.com> Author: Andrew Dunstan <andrew@dunslane.net> 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/CAF4Au4w2x-5LTnN_bxky-mq4=WOqsGsxSpENCzHRAzSnEd8+WQ@mail.gmail.com Discussion: https://postgr.es/m/cd0bb935-0158-78a7-08b5-904886deac4b@postgrespro.ru Discussion: https://postgr.es/m/20220616233130.rparivafipt6doj3@alap3.anarazel.de Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
This commit is contained in:
@ -655,6 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
json_array_aggregate_order_by_clause_opt
|
||||
|
||||
%type <ival> json_encoding_clause_opt
|
||||
json_predicate_type_constraint
|
||||
|
||||
%type <boolean> json_key_uniqueness_constraint_opt
|
||||
json_object_constructor_null_clause_opt
|
||||
@ -754,7 +755,8 @@ 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
|
||||
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
|
||||
@ -818,6 +820,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
|
||||
%nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
|
||||
%nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
|
||||
%nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
|
||||
|
||||
/* SQL/JSON related keywords */
|
||||
%nonassoc UNIQUE JSON
|
||||
%nonassoc KEYS OBJECT_P SCALAR VALUE_P
|
||||
%nonassoc WITH WITHOUT_LA
|
||||
|
||||
/*
|
||||
* To support target_el without AS, it used to be necessary to assign IDENT an
|
||||
* explicit precedence just less than Op. While that's not really necessary
|
||||
@ -14850,6 +14858,44 @@ a_expr: c_expr { $$ = $1; }
|
||||
@2),
|
||||
@2);
|
||||
}
|
||||
| a_expr IS json_predicate_type_constraint
|
||||
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 SQL/JSON, but there are conflicts
|
||||
| a_expr
|
||||
FORMAT_LA JSON json_encoding_clause_opt
|
||||
IS json_predicate_type_constraint
|
||||
json_key_uniqueness_constraint_opt %prec IS
|
||||
{
|
||||
$3.location = @2;
|
||||
$$ = makeJsonIsPredicate($1, $3, $5, $6, @1);
|
||||
}
|
||||
*/
|
||||
| a_expr IS NOT
|
||||
json_predicate_type_constraint
|
||||
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 SQL/JSON, but there are conflicts
|
||||
| a_expr
|
||||
FORMAT_LA JSON json_encoding_clause_opt
|
||||
IS NOT
|
||||
json_predicate_type_constraint
|
||||
json_key_uniqueness_constraint_opt %prec IS
|
||||
{
|
||||
$3.location = @2;
|
||||
$$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1);
|
||||
}
|
||||
*/
|
||||
| DEFAULT
|
||||
{
|
||||
/*
|
||||
@ -16406,13 +16452,21 @@ json_output_clause_opt:
|
||||
| /* EMPTY */ { $$ = NULL; }
|
||||
;
|
||||
|
||||
json_predicate_type_constraint:
|
||||
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; }
|
||||
;
|
||||
|
||||
/* KEYS is a noise word here */
|
||||
json_key_uniqueness_constraint_opt:
|
||||
WITH UNIQUE KEYS { $$ = true; }
|
||||
| WITH UNIQUE { $$ = true; }
|
||||
| WITHOUT_LA UNIQUE KEYS { $$ = false; }
|
||||
| WITHOUT_LA UNIQUE { $$ = false; }
|
||||
| /* EMPTY */ { $$ = false; }
|
||||
WITH UNIQUE KEYS { $$ = true; }
|
||||
| WITH UNIQUE { $$ = true; }
|
||||
| WITHOUT_LA UNIQUE KEYS { $$ = false; }
|
||||
| WITHOUT_LA UNIQUE { $$ = false; }
|
||||
| /* EMPTY */ %prec KEYS { $$ = false; }
|
||||
;
|
||||
|
||||
json_name_and_value_list:
|
||||
@ -17182,6 +17236,7 @@ unreserved_keyword:
|
||||
| ROWS
|
||||
| RULE
|
||||
| SAVEPOINT
|
||||
| SCALAR
|
||||
| SCHEMA
|
||||
| SCHEMAS
|
||||
| SCROLL
|
||||
@ -17784,6 +17839,7 @@ bare_label_keyword:
|
||||
| ROWS
|
||||
| RULE
|
||||
| SAVEPOINT
|
||||
| SCALAR
|
||||
| SCHEMA
|
||||
| SCHEMAS
|
||||
| SCROLL
|
||||
|
@ -83,6 +83,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,
|
||||
@ -325,6 +326,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));
|
||||
@ -3818,3 +3823,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.
|
||||
*/
|
||||
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))));
|
||||
|
||||
/* This intentionally(?) drops the format clause. */
|
||||
return makeJsonIsPredicate(expr, NULL, pred->item_type,
|
||||
pred->unique_keys, pred->location);
|
||||
}
|
||||
|
Reference in New Issue
Block a user