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;