From f213131f2024bcb85aea7d2a7dfadef6a0ee1b31 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 28 Sep 2006 20:51:43 +0000
Subject: [PATCH] Fix IS NULL and IS NOT NULL tests on row-valued expressions
 to conform to the SQL spec, viz IS NULL is true if all the row's fields are
 null, IS NOT NULL is true if all the row's fields are not null.  The former
 coding got this right for a limited number of cases with IS NULL (ie, those
 where it could disassemble a ROW constructor at parse time), but was entirely
 wrong for IS NOT NULL.  Per report from Teodor.

I desisted from changing the behavior for arrays, since on closer inspection
it's not clear that there's any support for that in the SQL spec.  This
probably needs more consideration.
---
 doc/src/sgml/func.sgml                |  15 +++-
 src/backend/executor/execQual.c       | 100 +++++++++++++++++++-------
 src/backend/optimizer/util/clauses.c  |  81 ++++++++++++++++++++-
 src/backend/optimizer/util/predtest.c |  43 ++++++-----
 src/backend/parser/gram.y             | 100 ++++++--------------------
 src/backend/utils/cache/lsyscache.c   |  14 +++-
 src/include/nodes/execnodes.h         |  15 +++-
 src/include/nodes/nodes.h             |   3 +-
 src/include/nodes/primnodes.h         |   9 ++-
 src/include/utils/lsyscache.h         |   3 +-
 10 files changed, 253 insertions(+), 130 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b3fe74fe502..978309d59ea 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.340 2006/09/22 16:20:00 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.341 2006/09/28 20:51:41 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -338,6 +338,19 @@
    </para>
   </tip>
 
+  <note>
+   <para>
+    If the <replaceable>expression</replaceable> is row-valued, then
+    <literal>IS NULL</> is true when the row expression itself is null
+    or when all the row's fields are null, while
+    <literal>IS NOT NULL</> is true when the row expression itself is non-null
+    and all the row's fields are non-null.
+    This definition conforms to the SQL standard, and is a change from the
+    inconsistent behavior exhibited by <productname>PostgreSQL</productname>
+    versions prior to 8.2.
+   </para>
+  </note>
+
    <para>
     <indexterm>
      <primary>IS DISTINCT FROM</primary>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 7f341940d67..7e9e51f1391 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.193 2006/07/27 19:52:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.194 2006/09/28 20:51:41 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,7 +119,7 @@ static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
 static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
 			   ExprContext *econtext,
 			   bool *isNull, ExprDoneCond *isDone);
-static Datum ExecEvalNullTest(GenericExprState *nstate,
+static Datum ExecEvalNullTest(NullTestState *nstate,
 				 ExprContext *econtext,
 				 bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalBooleanTest(GenericExprState *bstate,
@@ -1247,8 +1247,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 
 	funcrettype = exprType((Node *) funcexpr->expr);
 
-	returnsTuple = (funcrettype == RECORDOID ||
-					get_typtype(funcrettype) == 'c');
+	returnsTuple = type_is_rowtype(funcrettype);
 
 	/*
 	 * Prepare a resultinfo node for communication.  We always do this even if
@@ -2683,7 +2682,7 @@ ExecEvalNullIf(FuncExprState *nullIfExpr,
  * ----------------------------------------------------------------
  */
 static Datum
-ExecEvalNullTest(GenericExprState *nstate,
+ExecEvalNullTest(NullTestState *nstate,
 				 ExprContext *econtext,
 				 bool *isNull,
 				 ExprDoneCond *isDone)
@@ -2696,28 +2695,77 @@ ExecEvalNullTest(GenericExprState *nstate,
 	if (isDone && *isDone == ExprEndResult)
 		return result;			/* nothing to check */
 
-	switch (ntest->nulltesttype)
+	if (nstate->argisrow && !(*isNull))
 	{
-		case IS_NULL:
-			if (*isNull)
+		HeapTupleHeader tuple;
+		Oid			tupType;
+		int32		tupTypmod;
+		TupleDesc	tupDesc;
+		HeapTupleData tmptup;
+		int			att;
+
+		tuple = DatumGetHeapTupleHeader(result);
+
+		tupType = HeapTupleHeaderGetTypeId(tuple);
+		tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+		/* Lookup tupdesc if first time through or if type changes */
+		tupDesc = get_cached_rowtype(tupType, tupTypmod,
+									 &nstate->argdesc, econtext);
+
+		/*
+		 * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
+		 */
+		tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+		tmptup.t_data = tuple;
+
+		for (att = 1; att <= tupDesc->natts; att++)
+		{
+			/* ignore dropped columns */
+			if (tupDesc->attrs[att-1]->attisdropped)
+				continue;
+			if (heap_attisnull(&tmptup, att))
 			{
-				*isNull = false;
-				return BoolGetDatum(true);
+				/* null field disproves IS NOT NULL */
+				if (ntest->nulltesttype == IS_NOT_NULL)
+					return BoolGetDatum(false);
 			}
 			else
-				return BoolGetDatum(false);
-		case IS_NOT_NULL:
-			if (*isNull)
 			{
-				*isNull = false;
-				return BoolGetDatum(false);
+				/* non-null field disproves IS NULL */
+				if (ntest->nulltesttype == IS_NULL)
+					return BoolGetDatum(false);
 			}
-			else
-				return BoolGetDatum(true);
-		default:
-			elog(ERROR, "unrecognized nulltesttype: %d",
-				 (int) ntest->nulltesttype);
-			return (Datum) 0;	/* keep compiler quiet */
+		}
+
+		return BoolGetDatum(true);
+	}
+	else
+	{
+		/* Simple scalar-argument case, or a null rowtype datum */
+		switch (ntest->nulltesttype)
+		{
+			case IS_NULL:
+				if (*isNull)
+				{
+					*isNull = false;
+					return BoolGetDatum(true);
+				}
+				else
+					return BoolGetDatum(false);
+			case IS_NOT_NULL:
+				if (*isNull)
+				{
+					*isNull = false;
+					return BoolGetDatum(false);
+				}
+				else
+					return BoolGetDatum(true);
+			default:
+				elog(ERROR, "unrecognized nulltesttype: %d",
+					 (int) ntest->nulltesttype);
+				return (Datum) 0;	/* keep compiler quiet */
+		}
 	}
 }
 
@@ -3609,11 +3657,13 @@ ExecInitExpr(Expr *node, PlanState *parent)
 		case T_NullTest:
 			{
 				NullTest   *ntest = (NullTest *) node;
-				GenericExprState *gstate = makeNode(GenericExprState);
+				NullTestState *nstate = makeNode(NullTestState);
 
-				gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
-				gstate->arg = ExecInitExpr(ntest->arg, parent);
-				state = (ExprState *) gstate;
+				nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
+				nstate->arg = ExecInitExpr(ntest->arg, parent);
+				nstate->argisrow = type_is_rowtype(exprType((Node *) ntest->arg));
+				nstate->argdesc = NULL;
+				state = (ExprState *) nstate;
 			}
 			break;
 		case T_BooleanTest:
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 48b125a774c..99d3147aeb5 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.220 2006/09/06 20:40:47 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.221 2006/09/28 20:51:41 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -2099,6 +2099,85 @@ eval_const_expressions_mutator(Node *node,
 		newfselect->resulttypmod = fselect->resulttypmod;
 		return (Node *) newfselect;
 	}
+	if (IsA(node, NullTest))
+	{
+		NullTest   *ntest = (NullTest *) node;
+		NullTest   *newntest;
+		Node	   *arg;
+
+		arg = eval_const_expressions_mutator((Node *) ntest->arg,
+											 context);
+		if (arg && IsA(arg, RowExpr))
+		{
+			RowExpr  *rarg = (RowExpr *) arg;
+			List	*newargs = NIL;
+			ListCell *l;
+
+			/*
+			 * We break ROW(...) IS [NOT] NULL into separate tests on its
+			 * component fields.  This form is usually more efficient to
+			 * evaluate, as well as being more amenable to optimization.
+			 */
+			foreach(l, rarg->args)
+			{
+				Node *relem = (Node *) lfirst(l);
+
+				/*
+				 * A constant field refutes the whole NullTest if it's of
+				 * the wrong nullness; else we can discard it.
+				 */
+				if (relem && IsA(relem, Const))
+				{
+					Const  *carg = (Const *) relem;
+
+					if (carg->constisnull ?
+						(ntest->nulltesttype == IS_NOT_NULL) :
+						(ntest->nulltesttype == IS_NULL))
+						return makeBoolConst(false, false);
+					continue;
+				}
+				newntest = makeNode(NullTest);
+				newntest->arg = (Expr *) relem;
+				newntest->nulltesttype = ntest->nulltesttype;
+				newargs = lappend(newargs, newntest);
+			}
+			/* If all the inputs were constants, result is TRUE */
+			if (newargs == NIL)
+				return makeBoolConst(true, false);
+			/* If only one nonconst input, it's the result */
+			if (list_length(newargs) == 1)
+				return (Node *) linitial(newargs);
+			/* Else we need an AND node */
+			return (Node *) make_andclause(newargs);
+		}
+		if (arg && IsA(arg, Const))
+		{
+			Const  *carg = (Const *) arg;
+			bool	result;
+
+			switch (ntest->nulltesttype)
+			{
+				case IS_NULL:
+					result = carg->constisnull;
+					break;
+				case IS_NOT_NULL:
+					result = !carg->constisnull;
+					break;
+				default:
+					elog(ERROR, "unrecognized nulltesttype: %d",
+						 (int) ntest->nulltesttype);
+					result = false;	/* keep compiler quiet */
+					break;
+			}
+
+			return makeBoolConst(result, false);
+		}
+
+		newntest = makeNode(NullTest);
+		newntest->arg = (Expr *) arg;
+		newntest->nulltesttype = ntest->nulltesttype;
+		return (Node *) newntest;
+	}
 	if (IsA(node, BooleanTest))
 	{
 		BooleanTest *btest = (BooleanTest *) node;
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 418c7614121..b909e6d4bf2 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.8 2006/08/05 00:21:14 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.9 2006/09/28 20:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "executor/executor.h"
 #include "optimizer/clauses.h"
 #include "optimizer/predtest.h"
+#include "parser/parse_expr.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -931,14 +932,18 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause)
 	{
 		Expr	   *nonnullarg = ((NullTest *) predicate)->arg;
 
-		if (is_opclause(clause) &&
-			list_member(((OpExpr *) clause)->args, nonnullarg) &&
-			op_strict(((OpExpr *) clause)->opno))
-			return true;
-		if (is_funcclause(clause) &&
-			list_member(((FuncExpr *) clause)->args, nonnullarg) &&
-			func_strict(((FuncExpr *) clause)->funcid))
-			return true;
+		/* row IS NOT NULL does not act in the simple way we have in mind */
+		if (!type_is_rowtype(exprType((Node *) nonnullarg)))
+		{
+			if (is_opclause(clause) &&
+				list_member(((OpExpr *) clause)->args, nonnullarg) &&
+				op_strict(((OpExpr *) clause)->opno))
+				return true;
+			if (is_funcclause(clause) &&
+				list_member(((FuncExpr *) clause)->args, nonnullarg) &&
+				func_strict(((FuncExpr *) clause)->funcid))
+				return true;
+		}
 		return false;			/* we can't succeed below... */
 	}
 
@@ -978,14 +983,18 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause)
 	{
 		Expr	   *isnullarg = ((NullTest *) predicate)->arg;
 
-		if (is_opclause(clause) &&
-			list_member(((OpExpr *) clause)->args, isnullarg) &&
-			op_strict(((OpExpr *) clause)->opno))
-			return true;
-		if (is_funcclause(clause) &&
-			list_member(((FuncExpr *) clause)->args, isnullarg) &&
-			func_strict(((FuncExpr *) clause)->funcid))
-			return true;
+		/* row IS NULL does not act in the simple way we have in mind */
+		if (!type_is_rowtype(exprType((Node *) isnullarg)))
+		{
+			if (is_opclause(clause) &&
+				list_member(((OpExpr *) clause)->args, isnullarg) &&
+				op_strict(((OpExpr *) clause)->opno))
+				return true;
+			if (is_funcclause(clause) &&
+				list_member(((FuncExpr *) clause)->args, isnullarg) &&
+				func_strict(((FuncExpr *) clause)->funcid))
+				return true;
+		}
 		return false;			/* we can't succeed below... */
 	}
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e0d52887958..69a2af46265 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.565 2006/09/03 22:37:05 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.566 2006/09/28 20:51:42 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -94,7 +94,6 @@ static Node *makeStringConst(char *str, TypeName *typename);
 static Node *makeIntConst(int val);
 static Node *makeFloatConst(char *str);
 static Node *makeAConst(Value *v);
-static Node *makeRowNullTest(NullTestType test, RowExpr *row);
 static A_Const *makeBoolAConst(bool state);
 static FuncCall *makeOverlaps(List *largs, List *rargs, int location);
 static void check_qualified_name(List *names);
@@ -7037,53 +7036,33 @@ a_expr:		c_expr									{ $$ = $1; }
 			 *	a ISNULL
 			 *	a NOTNULL
 			 */
-			| a_expr ISNULL
-				{
-					if (IsA($1, RowExpr))
-						$$ = makeRowNullTest(IS_NULL, (RowExpr *) $1);
-					else
-					{
-						NullTest *n = makeNode(NullTest);
-						n->arg = (Expr *) $1;
-						n->nulltesttype = IS_NULL;
-						$$ = (Node *)n;
-					}
-				}
 			| a_expr IS NULL_P
 				{
-					if (IsA($1, RowExpr))
-						$$ = makeRowNullTest(IS_NULL, (RowExpr *) $1);
-					else
-					{
-						NullTest *n = makeNode(NullTest);
-						n->arg = (Expr *) $1;
-						n->nulltesttype = IS_NULL;
-						$$ = (Node *)n;
-					}
+					NullTest *n = makeNode(NullTest);
+					n->arg = (Expr *) $1;
+					n->nulltesttype = IS_NULL;
+					$$ = (Node *)n;
 				}
-			| a_expr NOTNULL
+			| a_expr ISNULL
 				{
-					if (IsA($1, RowExpr))
-						$$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1);
-					else
-					{
-						NullTest *n = makeNode(NullTest);
-						n->arg = (Expr *) $1;
-						n->nulltesttype = IS_NOT_NULL;
-						$$ = (Node *)n;
-					}
+					NullTest *n = makeNode(NullTest);
+					n->arg = (Expr *) $1;
+					n->nulltesttype = IS_NULL;
+					$$ = (Node *)n;
 				}
 			| a_expr IS NOT NULL_P
 				{
-					if (IsA($1, RowExpr))
-						$$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1);
-					else
-					{
-						NullTest *n = makeNode(NullTest);
-						n->arg = (Expr *) $1;
-						n->nulltesttype = IS_NOT_NULL;
-						$$ = (Node *)n;
-					}
+					NullTest *n = makeNode(NullTest);
+					n->arg = (Expr *) $1;
+					n->nulltesttype = IS_NOT_NULL;
+					$$ = (Node *)n;
+				}
+			| a_expr NOTNULL
+				{
+					NullTest *n = makeNode(NullTest);
+					n->arg = (Expr *) $1;
+					n->nulltesttype = IS_NOT_NULL;
+					$$ = (Node *)n;
 				}
 			| row OVERLAPS row
 				{
@@ -9082,43 +9061,6 @@ makeBoolAConst(bool state)
 	return n;
 }
 
-/* makeRowNullTest()
- * Generate separate operator nodes for a single row descriptor test.
- *
- * Eventually this should be eliminated in favor of making the NullTest
- * node type capable of handling it directly.
- */
-static Node *
-makeRowNullTest(NullTestType test, RowExpr *row)
-{
-	Node		*result = NULL;
-	ListCell	*arg;
-
-	foreach(arg, row->args)
-	{
-		NullTest *n;
-
-		n = makeNode(NullTest);
-		n->arg = (Expr *) lfirst(arg);
-		n->nulltesttype = test;
-
-		if (result == NULL)
-			result = (Node *) n;
-		else if (test == IS_NOT_NULL)
-			result = (Node *) makeA_Expr(AEXPR_OR, NIL, result, (Node *)n, -1);
-		else
-			result = (Node *) makeA_Expr(AEXPR_AND, NIL, result, (Node *)n, -1);
-	}
-
-	if (result == NULL)
-	{
-		/* zero-length rows?  Generate constant TRUE or FALSE */
-		result = (Node *) makeBoolAConst(test == IS_NULL);
-	}
-
-	return result;
-}
-
 /* makeOverlaps()
  * Create and populate a FuncCall node to support the OVERLAPS operator.
  */
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 769206e02c8..53e3a5bf552 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.136 2006/08/15 22:36:17 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.137 2006/09/28 20:51:42 tgl Exp $
  *
  * NOTES
  *	  Eventually, the index information should go through here, too.
@@ -1716,6 +1716,18 @@ get_typtype(Oid typid)
 		return '\0';
 }
 
+/*
+ * type_is_rowtype
+ *
+ *		Convenience function to determine whether a type OID represents
+ *		a "rowtype" type --- either RECORD or a named composite type.
+ */
+bool
+type_is_rowtype(Oid typid)
+{
+	return (typid == RECORDOID || get_typtype(typid) == 'c');
+}
+
 /*
  * get_typ_typrelid
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 35ee8a20d06..5cbe5a54287 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.160 2006/08/25 04:06:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.161 2006/09/28 20:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -705,6 +705,19 @@ typedef struct MinMaxExprState
 	FmgrInfo	cfunc;			/* lookup info for comparison func */
 } MinMaxExprState;
 
+/* ----------------
+ *		NullTestState node
+ * ----------------
+ */
+typedef struct NullTestState
+{
+	ExprState	xprstate;
+	ExprState  *arg;			/* input expression */
+	bool		argisrow;		/* T if input is of a composite type */
+	/* used only if argisrow: */
+	TupleDesc	argdesc;		/* tupdesc for most recent input */
+} NullTestState;
+
 /* ----------------
  *		CoerceToDomainState node
  * ----------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index eb31fd2b6e7..c7abfba91a4 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.187 2006/08/02 01:59:47 joe Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.188 2006/09/28 20:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,6 +165,7 @@ typedef enum NodeTag
 	T_RowCompareExprState,
 	T_CoalesceExprState,
 	T_MinMaxExprState,
+	T_NullTestState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 17c3a894b0b..c84bab9287e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.115 2006/07/27 19:52:07 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.116 2006/09/28 20:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -717,9 +717,12 @@ typedef OpExpr NullIfExpr;
  * NullTest
  *
  * NullTest represents the operation of testing a value for NULLness.
- * Currently, we only support scalar input values, but eventually a
- * row-constructor input should be supported.
  * 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.  It would probably be a good idea to include an
+ * "argisrow" flag in the struct to reflect that, but for the moment,
+ * we do not do so to avoid forcing an initdb during 8.2beta.
  * ----------------
  */
 
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index ad2d9e30a62..208cba30476 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.105 2006/07/13 17:47:02 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.106 2006/09/28 20:51:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,6 +90,7 @@ extern void get_type_io_data(Oid typid,
 extern char get_typstorage(Oid typid);
 extern Node *get_typdefault(Oid typid);
 extern char get_typtype(Oid typid);
+extern bool type_is_rowtype(Oid typid);
 extern Oid	get_typ_typrelid(Oid typid);
 extern Oid	get_element_type(Oid typid);
 extern Oid	get_array_type(Oid typid);
-- 
GitLab