Index: src/backend/access/nbtree/nbtsearch.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/access/nbtree/nbtsearch.c,v retrieving revision 1.93 diff -u -r1.93 nbtsearch.c --- src/backend/access/nbtree/nbtsearch.c 19 Jun 2005 22:41:00 -0000 1.93 +++ src/backend/access/nbtree/nbtsearch.c 21 Sep 2005 21:44:34 -0000 @@ -647,13 +647,6 @@ ScanKey cur = startKeys[i]; /* - * _bt_preprocess_keys disallows it, but it's place to add some - * code later - */ - if (cur->sk_flags & SK_ISNULL) - elog(ERROR, "btree doesn't support is(not)null, yet"); - - /* * If scankey operator is of default subtype, we can use the * cached comparison procedure; otherwise gotta look it up in the * catalogs. Index: src/backend/access/nbtree/nbtutils.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/access/nbtree/nbtutils.c,v retrieving revision 1.63 diff -u -r1.63 nbtutils.c --- src/backend/access/nbtree/nbtutils.c 13 Jun 2005 23:14:48 -0000 1.63 +++ src/backend/access/nbtree/nbtutils.c 21 Sep 2005 21:44:34 -0000 @@ -257,12 +257,23 @@ if (numberOfKeys == 1) { /* - * We don't use indices for 'A is null' and 'A is not null' - * currently and 'A < = > <> NULL' will always fail - so qual is - * not OK if comparison value is NULL. - vadim 03/21/97 + * A IS (NOT) NULL is transformed to scankey with the + * SK_INDEXFINDNULL flag set. In this case the strategy + * determines if it IS NULL or IS NOT NULL. + * + * Note the use of SK_INDEXFINDNULL. This flag needs to be + * set for the index to find NULLs. This is to distinguish + * the static predicates (a = NULL) and (a IS NULL) and the + * runtime predicate (a = col) where col happend to be NULL + * this iteration. */ if (cur->sk_flags & SK_ISNULL) - so->qual_ok = false; + { + if( cur->sk_strategy != BTEqualStrategyNumber && cur->sk_strategy != BTLessStrategyNumber ) + so->qual_ok = false; + else if( !(cur->sk_flags & SK_INDEXFINDNULL) ) + so->qual_ok = false; + } else if (relation->rd_index->indisunique && relation->rd_rel->relnatts == 1) { @@ -308,13 +319,18 @@ /* See comments above: any NULL implies cannot match qual */ if (cur->sk_flags & SK_ISNULL) { - so->qual_ok = false; - - /* - * Quit processing so we don't try to invoke comparison - * routines on NULLs. - */ - return; + /* If it doesn't represent IS (NOT) NULL, it's always false */ + if( !(cur->sk_flags & SK_INDEXFINDNULL) ) + { + so->qual_ok = false; + return; + } + /* Check for expected strategies */ + if( cur->sk_strategy != BTEqualStrategyNumber && cur->sk_strategy != BTLessStrategyNumber ) + { + elog( ERROR, "Unrecognised NullTest in _bt_preprocesskeys" ); + return; + } } } @@ -345,6 +361,17 @@ if (!chk || j == (BTEqualStrategyNumber - 1)) continue; + + /* If we have a (col IS NULL), cannot accept any other predicates */ + if ( eq->sk_flags & SK_ISNULL ) + { + so->qual_ok = false; + break; + } + /* And a (col IS NOT NULL) is redundant with an equals */ + if( chk->sk_flags & SK_ISNULL ) + continue; + test = FunctionCall2(&chk->sk_func, eq->sk_argument, chk->sk_argument); @@ -375,13 +402,19 @@ ScanKey lt = xform[BTLessStrategyNumber - 1]; ScanKey le = xform[BTLessEqualStrategyNumber - 1]; - test = FunctionCall2(&le->sk_func, - lt->sk_argument, - le->sk_argument); - if (DatumGetBool(test)) - xform[BTLessEqualStrategyNumber - 1] = NULL; - else + /* An IS NOT NULL is redundant with le clause */ + if( lt->sk_flags & SK_ISNULL ) xform[BTLessStrategyNumber - 1] = NULL; + else + { + test = FunctionCall2(&le->sk_func, + lt->sk_argument, + le->sk_argument); + if (DatumGetBool(test)) + xform[BTLessEqualStrategyNumber - 1] = NULL; + else + xform[BTLessStrategyNumber - 1] = NULL; + } } /* keep only one of >, >= */ @@ -445,6 +478,46 @@ /* have we seen one of these before? */ if (xform[j]) { + /* If either arg is NULL, need special handling */ + if ( (cur->sk_flags | xform[j]->sk_flags) & SK_ISNULL) + { + if ( j == (BTEqualStrategyNumber - 1) ) /* IS NULL clause */ + { + /* If matching NULL, we can *only* match null */ + if ( (cur->sk_flags ^ xform[j]->sk_flags) & SK_ISNULL) + { + so->qual_ok = false; + continue; + } + /* If both = NULL, goto next attr */ + if( cur->sk_flags & SK_ISNULL ) + { + continue; + } + /* Should never reach here */ + } + /* IS NOT NULL is redundant with any other LT clause */ + if ( j == (BTLessStrategyNumber - 1) ) /* IS NOT NULL clause */ + { + /* Both IS NOT NULL, goto next attr */ + if ( (cur->sk_flags & xform[j]->sk_flags) & SK_ISNULL) + { + continue; + } + /* Keep the non-redundant one */ + if( xform[j]->sk_flags & SK_ISNULL ) + { + xform[j] = cur; + continue; + } + if( cur->sk_flags & SK_ISNULL ) + { + continue; + } + } + /* Should never get here */ + elog( ERROR, "Impossible case in _bt_preprocess_keys" ); + } /* yup, keep the more restrictive key */ test = FunctionCall2(&cur->sk_func, cur->sk_argument, @@ -515,12 +588,29 @@ tupdesc, &isNull); - /* btree doesn't support 'A is null' clauses, yet */ - if (key->sk_flags & SK_ISNULL) + if (key->sk_flags & SK_ISNULL && key->sk_strategy == BTEqualStrategyNumber ) { - /* we shouldn't get here, really; see _bt_preprocess_keys() */ - *continuescan = false; - return false; + /* This scankey is (col IS NULL). The comment below + * applies here too: if we're at a not null value on + * a required key, we must be at a point where it is + * no longer useful to proceed. */ + + if( !isNull && ikey < so->numberOfRequiredKeys ) + *continuescan = false; + + return isNull; + } + + if (key->sk_flags & SK_ISNULL && key->sk_strategy == BTLessStrategyNumber ) + { + /* This scankey is (col IS NOT NULL). As per comment + below, if we have a NULL value on a ForwardScan + we've reached the end of useful values.*/ + if (ikey < so->numberOfRequiredKeys && + ScanDirectionIsForward(dir)) + *continuescan = false; + + return !isNull; } if (isNull) Index: src/backend/executor/nodeIndexscan.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/executor/nodeIndexscan.c,v retrieving revision 1.103 diff -u -r1.103 nodeIndexscan.c --- src/backend/executor/nodeIndexscan.c 6 May 2005 17:24:54 -0000 1.103 +++ src/backend/executor/nodeIndexscan.c 21 Sep 2005 21:44:34 -0000 @@ -571,6 +571,38 @@ strategy_cell = lnext(strategy_cell); subtype = lfirst_oid(subtype_cell); subtype_cell = lnext(subtype_cell); + + if (IsA(clause, NullTest)) + { + leftop = (Expr *) ((NullTest *) clause)->arg; + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!(IsA(leftop, Var) && + var_is_rel((Var *) leftop))) + elog(ERROR, "NullTest doesn't have key"); + + varattno = ((Var *) leftop)->varattno; + + /* + * We need SK_INDEXFINDNULL for IS NULL to work, for + * runtime keys a NULL is special and shouldn't + * match anything. + * + * Note: for IS (NOT) NULL no procedure is needed. + */ + ScanKeyEntryInitializeWithInfo(&scan_keys[j], + SK_ISNULL | SK_INDEXFINDNULL, + varattno, /* attribute number to scan */ + strategy, /* op's strategy */ + subtype, /* strategy subtype */ + NULL, /* reg proc to use */ + (Datum)0 ); /* constant */ + continue; + } if (!IsA(clause, OpExpr)) elog(ERROR, "indexqual is not an OpExpr"); Index: src/backend/optimizer/path/indxpath.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v retrieving revision 1.188 diff -u -r1.188 indxpath.c --- src/backend/optimizer/path/indxpath.c 28 Aug 2005 22:47:20 -0000 1.188 +++ src/backend/optimizer/path/indxpath.c 21 Sep 2005 21:44:34 -0000 @@ -816,6 +816,14 @@ if (match_boolean_index_clause((Node *) clause, indexcol, index)) return true; } + + /* We can index NULL tests also, for btree only */ + if( index->relam == BTREE_AM_OID && IsA( clause, NullTest ) ) + { + leftop = (Node*) ((NullTest*)clause)->arg; + if (match_index_to_operand(leftop, indexcol, index)) + return true; + } /* Else clause must be a binary opclause. */ if (!is_opclause(clause)) @@ -1928,6 +1936,14 @@ continue; } } + + /* IS (NOT) NULL case */ + if( index->relam == BTREE_AM_OID && IsA( rinfo->clause, NullTest ) ) + { + resultquals = lappend(resultquals, make_restrictinfo( rinfo->clause, true, NULL ) ); + + continue; + } resultquals = list_concat(resultquals, expand_indexqual_condition(rinfo, Index: src/backend/optimizer/plan/createplan.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v retrieving revision 1.197 diff -u -r1.197 createplan.c --- src/backend/optimizer/plan/createplan.c 18 Aug 2005 17:51:11 -0000 1.197 +++ src/backend/optimizer/plan/createplan.c 21 Sep 2005 21:44:35 -0000 @@ -33,7 +33,7 @@ #include "parser/parse_expr.h" #include "utils/lsyscache.h" #include "utils/syscache.h" - +#include "access/skey.h" static Scan *create_scan_plan(PlannerInfo *root, Path *best_path); static List *build_relation_tlist(RelOptInfo *rel); @@ -1513,6 +1513,20 @@ Assert(IsA(rinfo, RestrictInfo)); clause = (OpExpr *) rinfo->clause; + if (IsA(clause, NullTest)) + { + NullTest *newc = copyObject((Node *) clause); + newc->arg = (Expr *)fix_indexqual_operand( (Node *)newc->arg, index, &opclass ); + /* Add to fixed index quals */ + *fixed_indexquals = lappend(*fixed_indexquals, newc); + /* IS NULL -> EqualStrategy, IS NOT NULL -> LessStrategy */ + *indexstrategy = lappend_int(*indexstrategy, (newc->nulltesttype == IS_NULL) ? BTEqualStrategyNumber : BTLessStrategyNumber ); + /* No subtype */ + *indexsubtype = lappend_oid(*indexsubtype, InvalidOid); + /* NullTests are never lossy */ + *nonlossy_indexquals = lappend(*nonlossy_indexquals, rinfo); + continue; + } if (!IsA(clause, OpExpr) || list_length(clause->args) != 2) elog(ERROR, "indexqual clause is not binary opclause"); Index: src/backend/utils/adt/selfuncs.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/selfuncs.c,v retrieving revision 1.187 diff -u -r1.187 selfuncs.c --- src/backend/utils/adt/selfuncs.c 21 Jul 2005 04:41:43 -0000 1.187 +++ src/backend/utils/adt/selfuncs.c 21 Sep 2005 21:44:35 -0000 @@ -4373,6 +4373,12 @@ Assert(IsA(rinfo, RestrictInfo)); clause = rinfo->clause; + if( IsA(clause, NullTest)) + { + /* NullTests don't need to do anything with commutators */ + indexBoundQuals = lappend(indexBoundQuals, rinfo); + continue; + } Assert(IsA(clause, OpExpr)); clause_op = ((OpExpr *) clause)->opno; if (match_index_to_operand(get_leftop(clause), indexcol, index)) Index: src/include/access/skey.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/access/skey.h,v retrieving revision 1.29 diff -u -r1.29 skey.h --- src/include/access/skey.h 24 Jun 2005 00:18:52 -0000 1.29 +++ src/include/access/skey.h 21 Sep 2005 21:44:36 -0000 @@ -73,7 +73,7 @@ #define SK_ISNULL 0x0001 /* sk_argument is NULL */ #define SK_UNARY 0x0002 /* unary operator (currently unsupported) */ #define SK_NEGATE 0x0004 /* must negate the function result */ - +#define SK_INDEXFINDNULL 0x0008 /* index can find NULL with SK_ISNULL here */ /* * prototypes for functions in access/common/scankey.c