From: Andrew Dunstan Date: Fri, 13 Mar 2026 03:51:26 +0000 (+0800) Subject: Allow IS JSON predicate to work with domain types X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3b4c2b9db25ee1c51e5133da529668a37aa02fc3;p=thirdparty%2Fpostgresql.git 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 Reviewed-by: Andrew Dunstan Reviewed-by: Kirill Reshke Discussion: https://postgr.es/m/CACJufxEk34DnJFG72CRsPPT4tsJL9arobX0tNPsn7yH28J=zQg@mail.gmail.com --- diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 98689649680..53ae0205702 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -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); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 2caec621d73..3cd35c5c457 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -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; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c2584249603..0b4a4911370 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 474caffad48..312dfdc182a 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -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); } /* diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 982ec25ae14..bf54d39feb0 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -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); diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6fdf8807533..b67e56e6c5a 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -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; diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out index 4e3b4540d42..f3be69838bf 100644 --- a/src/test/regress/expected/sqljson.out +++ b/src/test/regress/expected/sqljson.out @@ -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? ---------- diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql index 0ad7fb14e7d..5b2c4661556 100644 --- a/src/test/regress/sql/sqljson.sql +++ b/src/test/regress/sql/sqljson.sql @@ -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;