From 6d1efd76fb9852b8bc242dcaf35916090d7c5899 Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Wed, 19 Jan 2000 23:55:03 +0000 Subject: [PATCH] Fix handling of NULL constraint conditions: per SQL92 spec, a NULL result from a constraint condition does not violate the constraint (cf. discussion on pghackers 12/9/99). Implemented by adding a parameter to ExecQual, specifying whether to return TRUE or FALSE when the qual result is really NULL in three-valued boolean logic. Currently, ExecRelCheck is the only caller that asks for TRUE, but if we find any other places that have the wrong response to NULL, it'll be easy to fix them. --- src/backend/access/gist/gist.c | 6 +- src/backend/access/hash/hash.c | 6 +- src/backend/access/nbtree/nbtree.c | 6 +- src/backend/access/rtree/rtree.c | 6 +- src/backend/catalog/index.c | 6 +- src/backend/commands/copy.c | 4 +- src/backend/executor/execMain.c | 12 ++-- src/backend/executor/execQual.c | 68 ++++++++++++++++------ src/backend/executor/execScan.c | 7 ++- src/backend/executor/execUtils.c | 6 +- src/backend/executor/nodeAgg.c | 4 +- src/backend/executor/nodeHash.c | 7 +-- src/backend/executor/nodeHashjoin.c | 6 +- src/backend/executor/nodeIndexscan.c | 8 ++- src/backend/executor/nodeMergejoin.c | 26 ++++----- src/backend/executor/nodeNestloop.c | 6 +- src/backend/executor/nodeResult.c | 6 +- src/include/executor/executor.h | 17 +----- src/test/regress/input/constraints.source | 10 +++- src/test/regress/output/constraints.source | 30 +++++++--- 20 files changed, 143 insertions(+), 104 deletions(-) diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 9c9e8aeee7f..edebfcbebe3 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/gist/gist.c,v 1.49 2000/01/17 23:57:41 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/gist/gist.c,v 1.50 2000/01/19 23:54:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -169,7 +169,7 @@ gistbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) oldPred, econtext) == true) + if (ExecQual((List *) oldPred, econtext, false)) { ni++; continue; @@ -186,7 +186,7 @@ gistbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) pred, econtext) == false) + if (! ExecQual((List *) pred, econtext, false)) continue; #endif /* OMIT_PARTIAL_INDEX */ } diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index fd3e3f94d50..1d88bfc8bc4 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/hash/hash.c,v 1.33 1999/12/10 03:55:43 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/hash/hash.c,v 1.34 2000/01/19 23:54:47 tgl Exp $ * * NOTES * This file contains only the public interface routines. @@ -131,7 +131,7 @@ hashbuild(Relation heap, /* SetSlotContents(slot, htup); */ #ifndef OMIT_PARTIAL_INDEX slot->val = htup; - if (ExecQual((List *) oldPred, econtext) == true) + if (ExecQual((List *) oldPred, econtext, false)) { nitups++; continue; @@ -148,7 +148,7 @@ hashbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) pred, econtext) == false) + if (! ExecQual((List *) pred, econtext, false)) continue; #endif /* OMIT_PARTIAL_INDEX */ } diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 458b427c70d..1be5c08f973 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -11,7 +11,7 @@ * Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtree.c,v 1.50 1999/12/10 03:55:44 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/nbtree/nbtree.c,v 1.51 2000/01/19 23:54:48 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -167,7 +167,7 @@ btbuild(Relation heap, /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) oldPred, econtext) == true) + if (ExecQual((List *) oldPred, econtext, false)) { nitups++; continue; @@ -184,7 +184,7 @@ btbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) pred, econtext) == false) + if (! ExecQual((List *) pred, econtext, false)) continue; #endif /* OMIT_PARTIAL_INDEX */ } diff --git a/src/backend/access/rtree/rtree.c b/src/backend/access/rtree/rtree.c index 09930e1e340..fd2a8e545a4 100644 --- a/src/backend/access/rtree/rtree.c +++ b/src/backend/access/rtree/rtree.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtree.c,v 1.41 1999/12/10 03:55:45 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/rtree/Attic/rtree.c,v 1.42 2000/01/19 23:54:50 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -163,7 +163,7 @@ rtbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) oldPred, econtext) == true) + if (ExecQual((List *) oldPred, econtext, false)) { ni++; continue; @@ -180,7 +180,7 @@ rtbuild(Relation heap, #ifndef OMIT_PARTIAL_INDEX /* SetSlotContents(slot, htup); */ slot->val = htup; - if (ExecQual((List *) pred, econtext) == false) + if (! ExecQual((List *) pred, econtext, false)) continue; #endif /* OMIT_PARTIAL_INDEX */ } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 84009d6282b..375aed9a006 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.102 2000/01/17 23:57:43 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.103 2000/01/19 23:54:51 tgl Exp $ * * * INTERFACE ROUTINES @@ -1590,7 +1590,7 @@ DefaultBuild(Relation heapRelation, { /* SetSlotContents(slot, heapTuple); */ slot->val = heapTuple; - if (ExecQual((List *) oldPred, econtext) == true) + if (ExecQual((List *) oldPred, econtext, false)) { indtuples++; continue; @@ -1605,7 +1605,7 @@ DefaultBuild(Relation heapRelation, { /* SetSlotContents(slot, heapTuple); */ slot->val = heapTuple; - if (ExecQual((List *) predicate, econtext) == false) + if (! ExecQual((List *) predicate, econtext, false)) continue; } #endif /* OMIT_PARTIAL_INDEX */ diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 328b2d644eb..60611398173 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -6,7 +6,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.96 2000/01/16 21:37:50 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.97 2000/01/19 23:54:56 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -886,7 +886,7 @@ CopyFrom(Relation rel, bool binary, bool oids, FILE *fp, char *delim, char *null */ slot->val = tuple; /* SetSlotContents(slot, tuple); */ - if (ExecQual((List *) indexPred[i], econtext) == false) + if (! ExecQual((List *) indexPred[i], econtext, false)) continue; #endif /* OMIT_PARTIAL_INDEX */ } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index fa98950ac1f..863c13b64ec 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.105 2000/01/17 23:57:45 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.106 2000/01/19 23:54:53 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1487,7 +1487,6 @@ ExecRelCheck(Relation rel, HeapTuple tuple, EState *estate) RangeTblEntry *rte = makeNode(RangeTblEntry); List *rtlist; List *qual; - bool res; int i; slot->val = tuple; @@ -1526,9 +1525,12 @@ ExecRelCheck(Relation rel, HeapTuple tuple, EState *estate) { qual = estate->es_result_relation_constraints[i]; - res = ExecQual(qual, econtext); - - if (!res) + /* + * NOTE: SQL92 specifies that a NULL result from a constraint + * expression is not to be treated as a failure. Therefore, + * tell ExecQual to return TRUE for NULL. + */ + if (! ExecQual(qual, econtext, true)) return check[i].ccname; } diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index bd546597733..9e9cbde83bf 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.65 2000/01/10 17:14:34 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.66 2000/01/19 23:54:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1283,12 +1283,33 @@ ExecEvalExpr(Node *expression, /* ---------------------------------------------------------------- * ExecQual * - * Evaluates a conjunctive boolean expression and returns t - * iff none of the subexpressions are false (or null). + * Evaluates a conjunctive boolean expression (qual list) and + * returns true iff none of the subexpressions are false. + * (We also return true if the list is empty.) + * + * If some of the subexpressions yield NULL but none yield FALSE, + * then the result of the conjunction is NULL (ie, unknown) + * according to three-valued boolean logic. In this case, + * we return the value specified by the "resultForNull" parameter. + * + * Callers evaluating WHERE clauses should pass resultForNull=FALSE, + * since SQL specifies that tuples with null WHERE results do not + * get selected. On the other hand, callers evaluating constraint + * conditions should pass resultForNull=TRUE, since SQL also specifies + * that NULL constraint conditions are not failures. + * + * NOTE: it would not be correct to use this routine to evaluate an + * AND subclause of a boolean expression; for that purpose, a NULL + * result must be returned as NULL so that it can be properly treated + * in the next higher operator (cf. ExecEvalAnd and ExecEvalOr). + * This routine is only used in contexts where a complete expression + * is being evaluated and we know that NULL can be treated the same + * as one boolean result or the other. + * * ---------------------------------------------------------------- */ bool -ExecQual(List *qual, ExprContext *econtext) +ExecQual(List *qual, ExprContext *econtext, bool resultForNull) { List *qlist; @@ -1302,18 +1323,18 @@ ExecQual(List *qual, ExprContext *econtext) IncrProcessed(); /* - * a "qual" is a list of clauses. To evaluate the qual, we evaluate - * each of the clauses in the list. (For an empty list, we'll return - * TRUE.) + * Evaluate the qual conditions one at a time. If we find a FALSE + * result, we can stop evaluating and return FALSE --- the AND result + * must be FALSE. Also, if we find a NULL result when resultForNull + * is FALSE, we can stop and return FALSE --- the AND result must be + * FALSE or NULL in that case, and the caller doesn't care which. * - * If any of the clauses return NULL, we treat this as FALSE. This - * is correct per the SQL spec: if any ANDed conditions are NULL, then - * the AND result is either FALSE or NULL, and in either case the - * WHERE condition fails. NOTE: it would NOT be correct to use this - * simplified logic in a sub-clause; ExecEvalAnd must do the full - * three-state condition evaluation. We can get away with simpler - * logic here because we know how the result will be used. + * If we get to the end of the list, we can return TRUE. This will + * happen when the AND result is indeed TRUE, or when the AND result + * is NULL (one or more NULL subresult, with all the rest TRUE) and + * the caller has specified resultForNull = TRUE. */ + foreach(qlist, qual) { Node *clause = (Node *) lfirst(qlist); @@ -1321,7 +1342,11 @@ ExecQual(List *qual, ExprContext *econtext) bool isNull; bool isDone; - /* if there is a null clause, consider the qualification to fail */ + /* + * If there is a null clause, consider the qualification to fail. + * XXX is this still correct for constraints? It probably shouldn't + * happen at all ... + */ if (clause == NULL) return false; /* @@ -1329,10 +1354,17 @@ ExecQual(List *qual, ExprContext *econtext) * in the qualifications. */ expr_value = ExecEvalExpr(clause, econtext, &isNull, &isDone); + if (isNull) - return false; /* treat NULL as FALSE */ - if (DatumGetInt32(expr_value) == 0) - return false; + { + if (resultForNull == false) + return false; /* treat NULL as FALSE */ + } + else + { + if (DatumGetInt32(expr_value) == 0) + return false; /* definitely FALSE */ + } } return true; diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 4196fb7a8a4..4803653e14c 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execScan.c,v 1.9 1999/02/13 23:15:18 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execScan.c,v 1.10 2000/01/19 23:54:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -137,9 +137,10 @@ ExecScan(Scan *node, /* * add a check for non-nil qual here to avoid a function call to - * ExecQual() when the qual is nil + * ExecQual() when the qual is nil ... saves only a few cycles, + * but they add up ... */ - if (!qual || ExecQual(qual, econtext) == true) + if (!qual || ExecQual(qual, econtext, false)) break; } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d168071e1fe..4d5079ae694 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.51 1999/12/20 10:40:42 wieck Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.52 2000/01/19 23:54:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1137,7 +1137,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot, IndexInfo **indexInfoArray; IndexInfo *indexInfo; Node *predicate; - bool satisfied; ExprContext *econtext; InsertIndexResult result; int numberOfAttributes; @@ -1178,8 +1177,7 @@ ExecInsertIndexTuples(TupleTableSlot *slot, econtext->ecxt_scantuple = slot; /* Skip this index-update if the predicate isn't satisfied */ - satisfied = ExecQual((List *) predicate, econtext); - if (satisfied == false) + if (! ExecQual((List *) predicate, econtext, false)) continue; } diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 0a95c92347f..a40fd015af3 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -31,7 +31,7 @@ * Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.60 1999/12/13 01:26:52 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.61 2000/01/19 23:54:54 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -580,7 +580,7 @@ ExecAgg(Agg *node) * Otherwise, return the tuple. */ } - while (! ExecQual(node->plan.qual, econtext)); + while (! ExecQual(node->plan.qual, econtext, false)); return resultSlot; } diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index 1995048e2db..46307a2aa96 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -6,7 +6,7 @@ * Copyright (c) 1994, Regents of the University of California * * - * $Id: nodeHash.c,v 1.42 2000/01/09 00:26:18 tgl Exp $ + * $Id: nodeHash.c,v 1.43 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -584,7 +584,6 @@ ExecScanHashBucket(HashJoinState *hjstate, { HeapTuple heapTuple = &hashTuple->htup; TupleTableSlot *inntuple; - bool qualResult; /* insert hashtable's tuple into exec slot so ExecQual sees it */ inntuple = ExecStoreTuple(heapTuple, /* tuple to store */ @@ -593,9 +592,7 @@ ExecScanHashBucket(HashJoinState *hjstate, false); /* do not pfree this tuple */ econtext->ecxt_innertuple = inntuple; - qualResult = ExecQual(hjclauses, econtext); - - if (qualResult) + if (ExecQual(hjclauses, econtext, false)) { hjstate->hj_CurTuple = hashTuple; return heapTuple; diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 9d5034307fe..6f5d2cae194 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.28 1999/12/16 22:19:44 wieck Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.29 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -54,7 +54,6 @@ ExecHashJoin(HashJoin *node) ExprContext *econtext; HashJoinTable hashtable; HeapTuple curtuple; - bool qualResult; TupleTableSlot *outerTupleSlot; TupleTableSlot *innerTupleSlot; Var *innerhashkey; @@ -220,14 +219,13 @@ ExecHashJoin(HashJoin *node) InvalidBuffer, false); /* don't pfree this tuple */ econtext->ecxt_innertuple = inntuple; - qualResult = ExecQual(qual, econtext); /* ---------------- * if we pass the qual, then save state for next call and * have ExecProject form the projection, store it * in the tuple table, and return the slot. * ---------------- */ - if (qualResult) + if (ExecQual(qual, econtext, false)) { ProjectionInfo *projInfo; TupleTableSlot *result; diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index b9e3cf58636..6ed14e0ad9a 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.43 1999/09/24 00:24:23 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v 1.44 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -137,7 +137,8 @@ IndexNext(IndexScan *node) { scanstate->cstate.cs_ExprContext->ecxt_scantuple = slot; if (ExecQual(nth(iptr, node->indxqualorig), - scanstate->cstate.cs_ExprContext)) + scanstate->cstate.cs_ExprContext, + false)) break; } if (iptr == numIndices) /* would not be returned by indices */ @@ -220,7 +221,8 @@ IndexNext(IndexScan *node) { scanstate->cstate.cs_ExprContext->ecxt_scantuple = slot; if (ExecQual(nth(prev_index, node->indxqualorig), - scanstate->cstate.cs_ExprContext)) + scanstate->cstate.cs_ExprContext, + false)) { prev_matches = true; break; diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index b15e135465d..59287c0f509 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.32 1999/11/22 17:56:03 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.33 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -186,12 +186,12 @@ MJFormSkipQual(List *qualList, char *replaceopname) * MergeCompare * * Compare the keys according to 'compareQual' which is of the - * form: {(key1a > key2a)(key1b > key2b) ...}. + * form: { (key1a > key2a) (key1b > key2b) ... }. * - * (actually, it could also be the form (key1a < key2a)..) + * (actually, it could also be of the form (key1a < key2a)...) * * This is different from calling ExecQual because ExecQual returns - * true only if ALL the comparisions clauses are satisfied. + * true only if ALL the comparison clauses are satisfied. * However, there is an order of significance among the keys with * the first keys being most significant. Therefore, the clauses * are evaluated in order and the 'compareQual' is satisfied @@ -217,14 +217,14 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext) /* ---------------- * for each pair of clauses, test them until - * our compare conditions are satisified + * our compare conditions are satisfied * ---------------- */ eqclause = eqQual; foreach(clause, compareQual) { /* ---------------- - * first test if our compare clause is satisified. + * first test if our compare clause is satisfied. * if so then return true. ignore isDone, don't iterate in * quals. * ---------------- @@ -255,7 +255,7 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext) /* ---------------- * if we get here then it means none of our key greater-than - * conditions were satisified so we return false. + * conditions were satisfied so we return false. * ---------------- */ return false; @@ -547,7 +547,7 @@ ExecMergeJoin(MergeJoin *node) case EXEC_MJ_JOINTEST: MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTEST\n"); - qualResult = ExecQual((List *) mergeclauses, econtext); + qualResult = ExecQual((List *) mergeclauses, econtext, false); MJ_DEBUG_QUAL(mergeclauses, qualResult); if (qualResult) @@ -558,14 +558,14 @@ ExecMergeJoin(MergeJoin *node) /* * EXEC_MJ_JOINTUPLES means we have two tuples which - * satisified the merge clause so we join them and then + * satisfied the merge clause so we join them and then * proceed to get the next inner tuple (EXEC_NEXT_INNER). */ case EXEC_MJ_JOINTUPLES: MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n"); mergestate->mj_JoinState = EXEC_MJ_NEXTINNER; - qualResult = ExecQual((List *) qual, econtext); + qualResult = ExecQual((List *) qual, econtext, false); MJ_DEBUG_QUAL(qual, qualResult); if (qualResult) @@ -693,7 +693,7 @@ ExecMergeJoin(MergeJoin *node) innerTupleSlot = econtext->ecxt_innertuple; econtext->ecxt_innertuple = mergestate->mj_MarkedTupleSlot; - qualResult = ExecQual((List *) mergeclauses, econtext); + qualResult = ExecQual((List *) mergeclauses, econtext, false); MJ_DEBUG_QUAL(mergeclauses, qualResult); if (qualResult) @@ -777,7 +777,7 @@ ExecMergeJoin(MergeJoin *node) * we update the marked tuple and go join them. * ---------------- */ - qualResult = ExecQual((List *) mergeclauses, econtext); + qualResult = ExecQual((List *) mergeclauses, econtext, false); MJ_DEBUG_QUAL(mergeclauses, qualResult); if (qualResult) @@ -886,7 +886,7 @@ ExecMergeJoin(MergeJoin *node) * we update the marked tuple and go join them. * ---------------- */ - qualResult = ExecQual((List *) mergeclauses, econtext); + qualResult = ExecQual((List *) mergeclauses, econtext, false); MJ_DEBUG_QUAL(mergeclauses, qualResult); if (qualResult) diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index a75e82c0b11..861df1d6e0f 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.13 1999/07/16 04:58:51 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.14 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -65,7 +65,6 @@ ExecNestLoop(NestLoop *node, Plan *parent) TupleTableSlot *innerTupleSlot; List *qual; - bool qualResult; ExprContext *econtext; /* ---------------- @@ -208,9 +207,8 @@ ExecNestLoop(NestLoop *node, Plan *parent) * ---------------- */ ENL1_printf("testing qualification"); - qualResult = ExecQual((List *) qual, econtext); - if (qualResult) + if (ExecQual((List *) qual, econtext, false)) { /* ---------------- * qualification was satisified so we project and diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c index 7c8166c6f21..ac63d2af425 100644 --- a/src/backend/executor/nodeResult.c +++ b/src/backend/executor/nodeResult.c @@ -27,7 +27,7 @@ * SeqScan (emp.all) * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/nodeResult.c,v 1.11 1999/05/25 16:08:46 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/nodeResult.c,v 1.12 2000/01/19 23:54:55 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -79,7 +79,9 @@ ExecResult(Result *node) */ if (resstate->rs_checkqual) { - bool qualResult = ExecQual((List *) node->resconstantqual, econtext); + bool qualResult = ExecQual((List *) node->resconstantqual, + econtext, + false); resstate->rs_checkqual = false; if (qualResult == false) diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index af599330a09..88dcb741b3d 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -6,7 +6,7 @@ * * Copyright (c) 1994, Regents of the University of California * - * $Id: executor.h,v 1.40 1999/12/10 03:56:08 momjian Exp $ + * $Id: executor.h,v 1.41 2000/01/19 23:55:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,17 +24,7 @@ */ /* return: true if tuple in slot is NULL, slot is slot to test */ #define TupIsNull(slot) \ -( \ - ((slot) == NULL) ? \ - true \ - : \ - ( \ - ((slot)->val == NULL) ? \ - true \ - : \ - false \ - ) \ -) + ((slot) == NULL || (slot)->val == NULL) /* * prototypes from functions in execAmi.c @@ -88,13 +78,12 @@ extern Datum ExecExtractResult(TupleTableSlot *slot, AttrNumber attnum, extern Datum ExecEvalParam(Param *expression, ExprContext *econtext, bool *isNull); -/* stop here */ extern char *GetAttributeByNum(TupleTableSlot *slot, AttrNumber attrno, bool *isNull); extern char *GetAttributeByName(TupleTableSlot *slot, char *attname, bool *isNull); extern Datum ExecEvalExpr(Node *expression, ExprContext *econtext, bool *isNull, bool *isDone); -extern bool ExecQual(List *qual, ExprContext *econtext); +extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull); extern int ExecTargetListLength(List *targetlist); extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo, bool *isDone); diff --git a/src/test/regress/input/constraints.source b/src/test/regress/input/constraints.source index 5c39c825c1b..6acef5eb241 100644 --- a/src/test/regress/input/constraints.source +++ b/src/test/regress/input/constraints.source @@ -89,7 +89,6 @@ CREATE TABLE INSERT_TBL (x INT DEFAULT nextval('insert_seq'), CONSTRAINT INSERT_CON CHECK (x >= 3 AND y <> 'check failed' AND x < 8), CHECK (x + z = 0)); -INSERT INTO INSERT_TBL VALUES (null, null, null); INSERT INTO INSERT_TBL(x,z) VALUES (2, -2); SELECT '' AS zero, * FROM INSERT_TBL; @@ -119,6 +118,13 @@ INSERT INTO INSERT_TBL(y) VALUES ('Y'); SELECT 'eight' AS one, currval('insert_seq'); +-- According to SQL92, it is OK to insert a record that gives rise to NULL +-- constraint-condition results. Postgres used to reject this, but it +-- was wrong: +INSERT INTO INSERT_TBL VALUES (null, null, null); + +SELECT '' AS nine, * FROM INSERT_TBL; + -- -- Check inheritance of defaults and constraints -- @@ -166,7 +172,7 @@ DROP TABLE tmp; -- Check constraints on UPDATE -- -UPDATE INSERT_TBL SET x = NULL WHERE x = 6; +UPDATE INSERT_TBL SET x = NULL WHERE x = 5; UPDATE INSERT_TBL SET x = 6 WHERE x = 6; UPDATE INSERT_TBL SET x = -z, z = -x; UPDATE INSERT_TBL SET x = z, z = x; diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index f1a107759df..3b9bc2c56c0 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -106,8 +106,6 @@ CREATE TABLE INSERT_TBL (x INT DEFAULT nextval('insert_seq'), z INT DEFAULT -1 * currval('insert_seq'), CONSTRAINT INSERT_CON CHECK (x >= 3 AND y <> 'check failed' AND x < 8), CHECK (x + z = 0)); -INSERT INTO INSERT_TBL VALUES (null, null, null); -ERROR: ExecAppend: rejected due to CHECK constraint $2 INSERT INTO INSERT_TBL(x,z) VALUES (2, -2); ERROR: ExecAppend: rejected due to CHECK constraint insert_con SELECT '' AS zero, * FROM INSERT_TBL; @@ -171,6 +169,22 @@ SELECT 'eight' AS one, currval('insert_seq'); eight | 8 (1 row) +-- According to SQL92, it is OK to insert a record that gives rise to NULL +-- constraint-condition results. Postgres used to reject this, but it +-- was wrong: +INSERT INTO INSERT_TBL VALUES (null, null, null); +SELECT '' AS nine, * FROM INSERT_TBL; + nine | x | y | z +------+---+---------------+---- + | 3 | Y | -3 + | 7 | -NULL- | -7 + | 7 | !check failed | -7 + | 4 | -!NULL- | -4 + | 5 | !check failed | -5 + | 6 | -!NULL- | -6 + | | | +(7 rows) + -- -- Check inheritance of defaults and constraints -- @@ -212,7 +226,6 @@ SELECT '' AS three, * FROM INSERT_TBL; (3 rows) INSERT INTO INSERT_TBL SELECT * FROM tmp WHERE yd = 'try again'; -ERROR: ExecAppend: rejected due to CHECK constraint $2 INSERT INTO INSERT_TBL(y,z) SELECT yd, -7 FROM tmp WHERE yd = 'try again'; INSERT INTO INSERT_TBL(y,z) SELECT yd, -8 FROM tmp WHERE yd = 'try again'; ERROR: ExecAppend: rejected due to CHECK constraint insert_con @@ -222,15 +235,15 @@ SELECT '' AS four, * FROM INSERT_TBL; | 4 | Y | -4 | 5 | !check failed | -5 | 6 | try again | -6 + | | try again | | 7 | try again | -7 -(4 rows) +(5 rows) DROP TABLE tmp; -- -- Check constraints on UPDATE -- -UPDATE INSERT_TBL SET x = NULL WHERE x = 6; -ERROR: ExecReplace: rejected due to CHECK constraint $2 +UPDATE INSERT_TBL SET x = NULL WHERE x = 5; UPDATE INSERT_TBL SET x = 6 WHERE x = 6; UPDATE INSERT_TBL SET x = -z, z = -x; UPDATE INSERT_TBL SET x = z, z = x; @@ -239,10 +252,11 @@ SELECT * FROM INSERT_TBL; x | y | z ---+---------------+---- 4 | Y | -4 - 5 | !check failed | -5 + | try again | 7 | try again | -7 + 5 | !check failed | 6 | try again | -6 -(4 rows) +(5 rows) -- DROP TABLE INSERT_TBL; -- -- GitLab