mirror of
https://github.com/postgres/postgres.git
synced 2026-06-10 09:10:33 -04:00
Allow IS JSON predicate to work with domain types
The IS JSON predicate only accepted the base types text, json, jsonb, and bytea. Extend it to also accept domain types over those base types by resolving through getBaseType() during parse analysis. The base type OID is stored in the JsonIsPredicate node (as exprBaseType) so the executor can dispatch to the correct validation path without repeating the domain lookup at runtime. When a non-supported type (or domain over a non-supported type) is used, the error message displays the original type name as written by the user, rather than the resolved base type. Author: jian he <jian.universality@gmail.com> Reviewed-by: Andrew Dunstan <andrew@dunslane.net> Reviewed-by: Kirill Reshke <reshkekirill@gmail.com> Discussion: https://postgr.es/m/CACJufxEk34DnJFG72CRsPPT4tsJL9arobX0tNPsn7yH28J=zQg@mail.gmail.com
This commit is contained in:
parent
f5eb854ab6
commit
3b4c2b9db2
8 changed files with 103 additions and 14 deletions
|
|
@ -4742,7 +4742,7 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
|
|||
{
|
||||
JsonIsPredicate *pred = op->d.is_json.pred;
|
||||
Datum js = *op->resvalue;
|
||||
Oid exprtype;
|
||||
Oid exprtype = pred->exprBaseType;
|
||||
bool res;
|
||||
|
||||
if (*op->resnull)
|
||||
|
|
@ -4751,8 +4751,6 @@ ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
|
|||
return;
|
||||
}
|
||||
|
||||
exprtype = exprType(pred->expr);
|
||||
|
||||
if (exprtype == TEXTOID || exprtype == JSONOID)
|
||||
{
|
||||
text *json = DatumGetTextP(js);
|
||||
|
|
|
|||
|
|
@ -984,7 +984,7 @@ makeJsonKeyValue(Node *key, Node *value)
|
|||
*/
|
||||
Node *
|
||||
makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
|
||||
bool unique_keys, int location)
|
||||
bool unique_keys, Oid exprBaseType, int location)
|
||||
{
|
||||
JsonIsPredicate *n = makeNode(JsonIsPredicate);
|
||||
|
||||
|
|
@ -992,6 +992,7 @@ makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
|
|||
n->format = format;
|
||||
n->item_type = item_type;
|
||||
n->unique_keys = unique_keys;
|
||||
n->exprBaseType = exprBaseType;
|
||||
n->location = location;
|
||||
|
||||
return (Node *) n;
|
||||
|
|
|
|||
|
|
@ -16161,7 +16161,7 @@ a_expr: c_expr { $$ = $1; }
|
|||
{
|
||||
JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
|
||||
|
||||
$$ = makeJsonIsPredicate($1, format, $3, $4, @1);
|
||||
$$ = makeJsonIsPredicate($1, format, $3, $4, InvalidOid, @1);
|
||||
}
|
||||
/*
|
||||
* Required by SQL/JSON, but there are conflicts
|
||||
|
|
@ -16170,7 +16170,7 @@ a_expr: c_expr { $$ = $1; }
|
|||
IS json_predicate_type_constraint
|
||||
json_key_uniqueness_constraint_opt %prec IS
|
||||
{
|
||||
$$ = makeJsonIsPredicate($1, $2, $4, $5, @1);
|
||||
$$ = makeJsonIsPredicate($1, $2, $4, $5, InvalidOid, @1);
|
||||
}
|
||||
*/
|
||||
| a_expr IS NOT
|
||||
|
|
@ -16179,7 +16179,7 @@ a_expr: c_expr { $$ = $1; }
|
|||
{
|
||||
JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
|
||||
|
||||
$$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1);
|
||||
$$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, InvalidOid, @1), @1);
|
||||
}
|
||||
/*
|
||||
* Required by SQL/JSON, but there are conflicts
|
||||
|
|
@ -16189,7 +16189,7 @@ a_expr: c_expr { $$ = $1; }
|
|||
json_predicate_type_constraint
|
||||
json_key_uniqueness_constraint_opt %prec IS
|
||||
{
|
||||
$$ = makeNotExpr(makeJsonIsPredicate($1, $2, $5, $6, @1), @1);
|
||||
$$ = makeNotExpr(makeJsonIsPredicate($1, $2, $5, $6, InvalidOid, @1), @1);
|
||||
}
|
||||
*/
|
||||
| DEFAULT
|
||||
|
|
|
|||
|
|
@ -4077,7 +4077,7 @@ transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format,
|
|||
Node *raw_expr = transformExprRecurse(pstate, jsexpr);
|
||||
Node *expr = raw_expr;
|
||||
|
||||
*exprtype = exprType(expr);
|
||||
*exprtype = getBaseType(exprType(expr));
|
||||
|
||||
/* prepare input document */
|
||||
if (*exprtype == BYTEAOID)
|
||||
|
|
@ -4130,13 +4130,14 @@ transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
|
|||
/* 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))));
|
||||
errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("cannot use type %s in IS JSON predicate",
|
||||
format_type_be(exprType(expr))),
|
||||
parser_errposition(pstate, exprLocation(expr)));
|
||||
|
||||
/* This intentionally(?) drops the format clause. */
|
||||
return makeJsonIsPredicate(expr, NULL, pred->item_type,
|
||||
pred->unique_keys, pred->location);
|
||||
pred->unique_keys, exprtype, pred->location);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ extern JsonValueExpr *makeJsonValueExpr(Expr *raw_expr, Expr *formatted_expr,
|
|||
extern Node *makeJsonKeyValue(Node *key, Node *value);
|
||||
extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format,
|
||||
JsonValueType item_type, bool unique_keys,
|
||||
int location);
|
||||
Oid exprBaseType, int location);
|
||||
extern JsonBehavior *makeJsonBehavior(JsonBehaviorType btype, Node *expr,
|
||||
int location);
|
||||
extern JsonTablePath *makeJsonTablePath(Const *pathvalue, char *pathname);
|
||||
|
|
|
|||
|
|
@ -1762,6 +1762,7 @@ typedef struct JsonIsPredicate
|
|||
JsonFormat *format; /* FORMAT clause, if specified */
|
||||
JsonValueType item_type; /* JSON item type */
|
||||
bool unique_keys; /* check key uniqueness? */
|
||||
Oid exprBaseType; /* base type of the subject expression */
|
||||
ParseLoc location; /* token location, or -1 if unknown */
|
||||
} JsonIsPredicate;
|
||||
|
||||
|
|
|
|||
|
|
@ -1313,6 +1313,63 @@ SELECT NULL::bytea IS JSON;
|
|||
|
||||
SELECT NULL::int IS JSON;
|
||||
ERROR: cannot use type integer in IS JSON predicate
|
||||
LINE 1: SELECT NULL::int IS JSON;
|
||||
^
|
||||
-- IS JSON with domain types
|
||||
CREATE DOMAIN jd1 AS json CHECK ((VALUE ->'a')::text <> '3');
|
||||
CREATE DOMAIN jd2 AS jsonb CHECK ((VALUE ->'a') = '1'::jsonb);
|
||||
CREATE DOMAIN jd3 AS text CHECK (VALUE <> 'a');
|
||||
CREATE DOMAIN jd4 AS bytea CHECK (VALUE <> '\x61');
|
||||
CREATE DOMAIN jd5 AS date CHECK (VALUE <> NULL);
|
||||
-- NULLs through domains should return NULL (not error)
|
||||
SELECT NULL::jd1 IS JSON, NULL::jd2 IS JSON, NULL::jd3 IS JSON, NULL::jd4 IS JSON;
|
||||
?column? | ?column? | ?column? | ?column?
|
||||
----------+----------+----------+----------
|
||||
| | |
|
||||
(1 row)
|
||||
|
||||
SELECT NULL::jd1 IS NOT JSON;
|
||||
?column?
|
||||
----------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- domain over unsupported base type should error
|
||||
SELECT NULL::jd5 IS JSON; -- error
|
||||
ERROR: cannot use type jd5 in IS JSON predicate
|
||||
LINE 1: SELECT NULL::jd5 IS JSON;
|
||||
^
|
||||
SELECT NULL::jd5 IS JSON WITH UNIQUE KEYS; -- error
|
||||
ERROR: cannot use type jd5 in IS JSON predicate
|
||||
LINE 1: SELECT NULL::jd5 IS JSON WITH UNIQUE KEYS;
|
||||
^
|
||||
-- domain constraint violation during cast
|
||||
SELECT a::jd2 IS JSON WITH UNIQUE KEYS as col1 FROM (VALUES('{"a": 1, "a": 2}')) s(a); -- error
|
||||
ERROR: value for domain jd2 violates check constraint "jd2_check"
|
||||
-- view creation and deparsing with domain IS JSON
|
||||
CREATE VIEW domain_isjson AS
|
||||
WITH cte(a) AS (VALUES('{"a": 1, "a": 2}'))
|
||||
SELECT a::jd1 IS JSON WITH UNIQUE KEYS as jd1,
|
||||
a::jd3 IS JSON WITH UNIQUE KEYS as jd3,
|
||||
a::jd4 IS JSON WITH UNIQUE KEYS as jd4
|
||||
FROM cte;
|
||||
\sv domain_isjson
|
||||
CREATE OR REPLACE VIEW public.domain_isjson AS
|
||||
WITH cte(a) AS (
|
||||
VALUES ('{"a": 1, "a": 2}'::text)
|
||||
)
|
||||
SELECT a::jd1 IS JSON WITH UNIQUE KEYS AS jd1,
|
||||
a::jd3 IS JSON WITH UNIQUE KEYS AS jd3,
|
||||
a::jd4 IS JSON WITH UNIQUE KEYS AS jd4
|
||||
FROM cte
|
||||
SELECT * FROM domain_isjson;
|
||||
jd1 | jd3 | jd4
|
||||
-----+-----+-----
|
||||
f | f | f
|
||||
(1 row)
|
||||
|
||||
DROP VIEW domain_isjson;
|
||||
DROP DOMAIN jd5, jd4, jd3, jd2, jd1;
|
||||
SELECT '' IS JSON;
|
||||
?column?
|
||||
----------
|
||||
|
|
|
|||
|
|
@ -484,6 +484,37 @@ SELECT NULL::text IS JSON;
|
|||
SELECT NULL::bytea IS JSON;
|
||||
SELECT NULL::int IS JSON;
|
||||
|
||||
-- IS JSON with domain types
|
||||
CREATE DOMAIN jd1 AS json CHECK ((VALUE ->'a')::text <> '3');
|
||||
CREATE DOMAIN jd2 AS jsonb CHECK ((VALUE ->'a') = '1'::jsonb);
|
||||
CREATE DOMAIN jd3 AS text CHECK (VALUE <> 'a');
|
||||
CREATE DOMAIN jd4 AS bytea CHECK (VALUE <> '\x61');
|
||||
CREATE DOMAIN jd5 AS date CHECK (VALUE <> NULL);
|
||||
|
||||
-- NULLs through domains should return NULL (not error)
|
||||
SELECT NULL::jd1 IS JSON, NULL::jd2 IS JSON, NULL::jd3 IS JSON, NULL::jd4 IS JSON;
|
||||
SELECT NULL::jd1 IS NOT JSON;
|
||||
|
||||
-- domain over unsupported base type should error
|
||||
SELECT NULL::jd5 IS JSON; -- error
|
||||
SELECT NULL::jd5 IS JSON WITH UNIQUE KEYS; -- error
|
||||
|
||||
-- domain constraint violation during cast
|
||||
SELECT a::jd2 IS JSON WITH UNIQUE KEYS as col1 FROM (VALUES('{"a": 1, "a": 2}')) s(a); -- error
|
||||
|
||||
-- view creation and deparsing with domain IS JSON
|
||||
CREATE VIEW domain_isjson AS
|
||||
WITH cte(a) AS (VALUES('{"a": 1, "a": 2}'))
|
||||
SELECT a::jd1 IS JSON WITH UNIQUE KEYS as jd1,
|
||||
a::jd3 IS JSON WITH UNIQUE KEYS as jd3,
|
||||
a::jd4 IS JSON WITH UNIQUE KEYS as jd4
|
||||
FROM cte;
|
||||
\sv domain_isjson
|
||||
SELECT * FROM domain_isjson;
|
||||
|
||||
DROP VIEW domain_isjson;
|
||||
DROP DOMAIN jd5, jd4, jd3, jd2, jd1;
|
||||
|
||||
SELECT '' IS JSON;
|
||||
|
||||
SELECT bytea '\x00' IS JSON;
|
||||
|
|
|
|||
Loading…
Reference in a new issue