1
0
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:
Alvaro Herrera
2023-03-31 22:34:04 +02:00
parent a2a0c7c29e
commit 6ee30209a6
25 changed files with 1031 additions and 68 deletions

View File

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

View File

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