mirror of
https://github.com/postgres/postgres.git
synced 2025-08-21 10:42:50 +03:00
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.
This commit is contained in:
@@ -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:
|
||||
|
@@ -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;
|
||||
|
@@ -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... */
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
14
src/backend/utils/cache/lsyscache.c
vendored
14
src/backend/utils/cache/lsyscache.c
vendored
@@ -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
|
||||
*
|
||||
|
Reference in New Issue
Block a user