aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/postgres_fdw/deparse.c23
-rw-r--r--src/backend/optimizer/util/plancat.c8
-rw-r--r--src/backend/utils/adt/ruleutils.c46
-rw-r--r--src/include/nodes/primnodes.h14
-rw-r--r--src/test/regress/expected/rowtypes.out6
5 files changed, 77 insertions, 20 deletions
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 8f09cd51ca8..dd25699d83f 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -1806,10 +1806,27 @@ deparseNullTest(NullTest *node, deparse_expr_cxt *context)
appendStringInfoChar(buf, '(');
deparseExpr(node->arg, context);
- if (node->nulltesttype == IS_NULL)
- appendStringInfoString(buf, " IS NULL)");
+
+ /*
+ * For scalar inputs, we prefer to print as IS [NOT] NULL, which is
+ * shorter and traditional. If it's a rowtype input but we're applying a
+ * scalar test, must print IS [NOT] DISTINCT FROM NULL to be semantically
+ * correct.
+ */
+ if (node->argisrow || !type_is_rowtype(exprType((Node *) node->arg)))
+ {
+ if (node->nulltesttype == IS_NULL)
+ appendStringInfoString(buf, " IS NULL)");
+ else
+ appendStringInfoString(buf, " IS NOT NULL)");
+ }
else
- appendStringInfoString(buf, " IS NOT NULL)");
+ {
+ if (node->nulltesttype == IS_NULL)
+ appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL)");
+ else
+ appendStringInfoString(buf, " IS DISTINCT FROM NULL)");
+ }
}
/*
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 2be0793cc0a..f037f90d985 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -1090,7 +1090,13 @@ get_relation_constraints(PlannerInfo *root,
att->attcollation,
0);
ntest->nulltesttype = IS_NOT_NULL;
- ntest->argisrow = type_is_rowtype(att->atttypid);
+
+ /*
+ * argisrow=false is correct even for a composite column,
+ * because attnotnull does not represent a SQL-spec IS NOT
+ * NULL test in such a case, just IS DISTINCT FROM NULL.
+ */
+ ntest->argisrow = false;
ntest->location = -1;
result = lappend(result, ntest);
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 97bdccf6ec6..8a6d0ad9661 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -7890,17 +7890,43 @@ get_rule_expr(Node *node, deparse_context *context,
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren((Node *) ntest->arg, context, true, node);
- switch (ntest->nulltesttype)
+
+ /*
+ * For scalar inputs, we prefer to print as IS [NOT] NULL,
+ * which is shorter and traditional. If it's a rowtype input
+ * but we're applying a scalar test, must print IS [NOT]
+ * DISTINCT FROM NULL to be semantically correct.
+ */
+ if (ntest->argisrow ||
+ !type_is_rowtype(exprType((Node *) ntest->arg)))
{
- case IS_NULL:
- appendStringInfoString(buf, " IS NULL");
- break;
- case IS_NOT_NULL:
- appendStringInfoString(buf, " IS NOT NULL");
- break;
- default:
- elog(ERROR, "unrecognized nulltesttype: %d",
- (int) ntest->nulltesttype);
+ switch (ntest->nulltesttype)
+ {
+ case IS_NULL:
+ appendStringInfoString(buf, " IS NULL");
+ break;
+ case IS_NOT_NULL:
+ appendStringInfoString(buf, " IS NOT NULL");
+ break;
+ default:
+ elog(ERROR, "unrecognized nulltesttype: %d",
+ (int) ntest->nulltesttype);
+ }
+ }
+ else
+ {
+ switch (ntest->nulltesttype)
+ {
+ case IS_NULL:
+ appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL");
+ break;
+ case IS_NOT_NULL:
+ appendStringInfoString(buf, " IS DISTINCT FROM NULL");
+ break;
+ default:
+ elog(ERROR, "unrecognized nulltesttype: %d",
+ (int) ntest->nulltesttype);
+ }
}
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c8b1f907a8f..a8a2c4e4e76 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1076,8 +1076,16 @@ typedef struct XmlExpr
* NullTest represents the operation of testing a value for NULLness.
* The appropriate test is performed and returned as a boolean Datum.
*
- * NOTE: the semantics of this for rowtype inputs are noticeably different
- * from the scalar case. We provide an "argisrow" flag to reflect that.
+ * When argisrow is false, this simply represents a test for the null value.
+ *
+ * When argisrow is true, the input expression must yield a rowtype, and
+ * the node implements "row IS [NOT] NULL" per the SQL standard. This
+ * includes checking individual fields for NULLness when the row datum
+ * itself isn't NULL.
+ *
+ * NOTE: the combination of a rowtype input and argisrow==false does NOT
+ * correspond to the SQL notation "row IS [NOT] NULL"; instead, this case
+ * represents the SQL notation "row IS [NOT] DISTINCT FROM NULL".
* ----------------
*/
@@ -1091,7 +1099,7 @@ typedef struct NullTest
Expr xpr;
Expr *arg; /* input expression */
NullTestType nulltesttype; /* IS NULL, IS NOT NULL */
- bool argisrow; /* T if input is of a composite type */
+ bool argisrow; /* T to perform field-by-field null checks */
int location; /* token location, or -1 if unknown */
} NullTest;
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 2971640b4bd..25b08281c85 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -664,10 +664,10 @@ explain (verbose, costs off)
select r, r is null as isnull, r is not null as isnotnull
from (values (1,row(1,2)), (1,row(null,null)), (1,null),
(null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b);
- QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Values Scan on "*VALUES*"
- Output: ROW("*VALUES*".column1, "*VALUES*".column2), (("*VALUES*".column1 IS NULL) AND ("*VALUES*".column2 IS NULL)), (("*VALUES*".column1 IS NOT NULL) AND ("*VALUES*".column2 IS NOT NULL))
+ Output: ROW("*VALUES*".column1, "*VALUES*".column2), (("*VALUES*".column1 IS NULL) AND ("*VALUES*".column2 IS NOT DISTINCT FROM NULL)), (("*VALUES*".column1 IS NOT NULL) AND ("*VALUES*".column2 IS DISTINCT FROM NULL))
(2 rows)
select r, r is null as isnull, r is not null as isnotnull