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 @@ - + Functions and Operators @@ -338,6 +338,19 @@ + + + If the expression is row-valued, then + IS NULL is true when the row expression itself is null + or when all the row's fields are null, while + 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 PostgreSQL + versions prior to 8.2. + + + IS DISTINCT FROM 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);