-<!-- $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>
</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>
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
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
* ----------------------------------------------------------------
*/
static Datum
-ExecEvalNullTest(GenericExprState *nstate,
+ExecEvalNullTest(NullTestState *nstate,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone)
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 */
+ }
}
}
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:
*
*
* 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
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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
{
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... */
}
{
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... */
}
*
*
* 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
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);
* 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
{
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.
*/
* 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.
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
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
* ----------------
* 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 $
*
*-------------------------------------------------------------------------
*/
T_RowCompareExprState,
T_CoalesceExprState,
T_MinMaxExprState,
+ T_NullTestState,
T_CoerceToDomainState,
T_DomainConstraintState,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
* ----------------
*/
* 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 $
*
*-------------------------------------------------------------------------
*/
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);