]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Allow IS JSON predicate to work with domain types
authorAndrew Dunstan <andrew@dunslane.net>
Fri, 13 Mar 2026 03:51:26 +0000 (11:51 +0800)
committerAndrew Dunstan <andrew@dunslane.net>
Tue, 17 Mar 2026 19:20:22 +0000 (15:20 -0400)
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

src/backend/executor/execExprInterp.c
src/backend/nodes/makefuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/include/nodes/makefuncs.h
src/include/nodes/primnodes.h
src/test/regress/expected/sqljson.out
src/test/regress/sql/sqljson.sql

index 98689649680c419f75fb09543b98bda9381a8f37..53ae0205702bac8301171e7c5110f4f874bc8c73 100644 (file)
@@ -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);
index 2caec621d73db2dd4440e17f8cfa44712d1e6e9d..3cd35c5c457ee7ad614cc68c24d308827230b04a 100644 (file)
@@ -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;
index c25842496039ce414ec8c4a9b7cf81bd68debbb3..0b4a4911370b8507c0ab25fcc43f5d9a30b24f59 100644 (file)
@@ -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
index 474caffad48f3e95afb3137177c0508ffa9e96f5..312dfdc182ad218681434a78030d91197249a7ed 100644 (file)
@@ -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);
 }
 
 /*
index 982ec25ae1441e23c605b0b2db28e83b59700e56..bf54d39feb05a6b1a4f266e973b51e44069e9476 100644 (file)
@@ -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);
index 6fdf8807533a930d1f2897ee5b30b26736b3bef5..b67e56e6c5abdf969ec9a9488e20e9183b247bcb 100644 (file)
@@ -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;
 
index 4e3b4540d42c7c74cde910223b0386e4a6c49386..f3be69838bfc9d18b2ee3dd9cb0c5dcd3075d988 100644 (file)
@@ -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? 
 ----------
index 0ad7fb14e7d07d274c7543f13a2b939cd4b69bfe..5b2c46615566f90a11a3485d0d7835353a297178 100644 (file)
@@ -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;