diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c index 8805d32de9b0d19a0f0ba29e968b0eaf93ea230e..618b643d7e00da7ffcc9ed115055ecd969fbeb99 100644 --- a/src/backend/access/nbtree/nbtsearch.c +++ b/src/backend/access/nbtree/nbtsearch.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsearch.c,v 1.101 2006/01/23 22:31:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtsearch.c,v 1.102 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -551,6 +551,10 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) * one we use --- by definition, they are either redundant or * contradictory. * + * In this loop, row-comparison keys are treated the same as keys on their + * first (leftmost) columns. We'll add on lower-order columns of the row + * comparison below, if possible. + * * The selected scan keys (at most one per index column) are remembered by * storing their addresses into the local startKeys[] array. *---------- @@ -657,44 +661,91 @@ _bt_first(IndexScanDesc scan, ScanDirection dir) { 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"); + Assert(cur->sk_attno == i+1); - /* - * If scankey operator is of default subtype, we can use the cached - * comparison procedure; otherwise gotta look it up in the catalogs. - */ - if (cur->sk_subtype == InvalidOid) + if (cur->sk_flags & SK_ROW_HEADER) { - FmgrInfo *procinfo; - - procinfo = index_getprocinfo(rel, i + 1, BTORDER_PROC); - ScanKeyEntryInitializeWithInfo(scankeys + i, - cur->sk_flags, - i + 1, - InvalidStrategy, - InvalidOid, - procinfo, - cur->sk_argument); + /* + * Row comparison header: look to the first row member instead. + * + * The member scankeys are already in insertion format (ie, they + * have sk_func = 3-way-comparison function), but we have to + * watch out for nulls, which _bt_preprocess_keys didn't check. + * A null in the first row member makes the condition unmatchable, + * just like qual_ok = false. + */ + cur = (ScanKey) DatumGetPointer(cur->sk_argument); + Assert(cur->sk_flags & SK_ROW_MEMBER); + if (cur->sk_flags & SK_ISNULL) + return false; + memcpy(scankeys + i, cur, sizeof(ScanKeyData)); + /* + * If the row comparison is the last positioning key we accepted, + * try to add additional keys from the lower-order row members. + * (If we accepted independent conditions on additional index + * columns, we use those instead --- doesn't seem worth trying to + * determine which is more restrictive.) Note that this is OK + * even if the row comparison is of ">" or "<" type, because the + * condition applied to all but the last row member is effectively + * ">=" or "<=", and so the extra keys don't break the positioning + * scheme. + */ + if (i == keysCount - 1) + { + while (!(cur->sk_flags & SK_ROW_END)) + { + cur++; + Assert(cur->sk_flags & SK_ROW_MEMBER); + if (cur->sk_attno != keysCount + 1) + break; /* out-of-sequence, can't use it */ + if (cur->sk_flags & SK_ISNULL) + break; /* can't use null keys */ + Assert(keysCount < INDEX_MAX_KEYS); + memcpy(scankeys + keysCount, cur, sizeof(ScanKeyData)); + keysCount++; + } + break; /* done with outer loop */ + } } else { - RegProcedure cmp_proc; - - cmp_proc = get_opclass_proc(rel->rd_indclass->values[i], - cur->sk_subtype, - BTORDER_PROC); - ScanKeyEntryInitialize(scankeys + i, - cur->sk_flags, - i + 1, - InvalidStrategy, - cur->sk_subtype, - cmp_proc, - cur->sk_argument); + /* + * Ordinary comparison key. Transform the search-style scan key + * to an insertion scan key by replacing the sk_func with the + * appropriate btree comparison function. + * + * If scankey operator is of default subtype, we can use the + * cached comparison function; otherwise gotta look it up in the + * catalogs. + */ + if (cur->sk_subtype == InvalidOid) + { + FmgrInfo *procinfo; + + procinfo = index_getprocinfo(rel, cur->sk_attno, BTORDER_PROC); + ScanKeyEntryInitializeWithInfo(scankeys + i, + cur->sk_flags, + cur->sk_attno, + InvalidStrategy, + InvalidOid, + procinfo, + cur->sk_argument); + } + else + { + RegProcedure cmp_proc; + + cmp_proc = get_opclass_proc(rel->rd_indclass->values[i], + cur->sk_subtype, + BTORDER_PROC); + ScanKeyEntryInitialize(scankeys + i, + cur->sk_flags, + cur->sk_attno, + InvalidStrategy, + cur->sk_subtype, + cmp_proc, + cur->sk_argument); + } } } diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index b715383871a98d2fc1847247a466799559877668..90f2f64c81f51dc6c87824199fc477f635988d6b 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtutils.c,v 1.69 2006/01/23 22:31:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtutils.c,v 1.70 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,12 @@ #include "executor/execdebug.h" +static void _bt_mark_scankey_required(ScanKey skey); +static bool _bt_check_rowcompare(ScanKey skey, + IndexTuple tuple, TupleDesc tupdesc, + ScanDirection dir, bool *continuescan); + + /* * _bt_mkscankey * Build an insertion scan key that contains comparison data from itup @@ -218,6 +224,17 @@ _bt_formitem(IndexTuple itup) * equality quals for all columns. In this case there can be at most one * (visible) matching tuple. index_getnext uses this to avoid uselessly * continuing the scan after finding one match. + * + * Row comparison keys are treated the same as comparisons to nondefault + * datatypes: we just transfer them into the preprocessed array without any + * editorialization. We can treat them the same as an ordinary inequality + * comparison on the row's first index column, for the purposes of the logic + * about required keys. + * + * Note: the reason we have to copy the preprocessed scan keys into private + * storage is that we are modifying the array based on comparisons of the + * key argument values, which could change on a rescan. Therefore we can't + * overwrite the caller's data structure. *---------- */ void @@ -273,26 +290,8 @@ _bt_preprocess_keys(IndexScanDesc scan) memcpy(outkeys, inkeys, sizeof(ScanKeyData)); so->numberOfKeys = 1; /* We can mark the qual as required if it's for first index col */ - if (outkeys->sk_attno == 1) - { - switch (outkeys->sk_strategy) - { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - outkeys->sk_flags |= SK_BT_REQFWD; - break; - case BTEqualStrategyNumber: - outkeys->sk_flags |= (SK_BT_REQFWD | SK_BT_REQBKWD); - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - outkeys->sk_flags |= SK_BT_REQBKWD; - break; - default: - elog(ERROR, "unrecognized StrategyNumber: %d", - (int) outkeys->sk_strategy); - } - } + if (cur->sk_attno == 1) + _bt_mark_scankey_required(outkeys); return; } @@ -325,6 +324,7 @@ _bt_preprocess_keys(IndexScanDesc scan) if (i < numberOfKeys) { /* See comments above: any NULL implies cannot match qual */ + /* Note: we assume SK_ISNULL is never set in a row header key */ if (cur->sk_flags & SK_ISNULL) { so->qual_ok = false; @@ -432,26 +432,7 @@ _bt_preprocess_keys(IndexScanDesc scan) memcpy(outkey, xform[j], sizeof(ScanKeyData)); if (priorNumberOfEqualCols == attno - 1) - { - switch (outkey->sk_strategy) - { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - outkey->sk_flags |= SK_BT_REQFWD; - break; - case BTEqualStrategyNumber: - outkey->sk_flags |= (SK_BT_REQFWD | - SK_BT_REQBKWD); - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - outkey->sk_flags |= SK_BT_REQBKWD; - break; - default: - elog(ERROR, "unrecognized StrategyNumber: %d", - (int) outkey->sk_strategy); - } - } + _bt_mark_scankey_required(outkey); } } @@ -470,11 +451,14 @@ _bt_preprocess_keys(IndexScanDesc scan) /* check strategy this key's operator corresponds to */ j = cur->sk_strategy - 1; - /* if wrong RHS data type, punt */ - if (cur->sk_subtype != InvalidOid) + /* if row comparison or wrong RHS data type, punt */ + if ((cur->sk_flags & SK_ROW_HEADER) || cur->sk_subtype != InvalidOid) { - memcpy(&outkeys[new_numberOfKeys++], cur, - sizeof(ScanKeyData)); + ScanKey outkey = &outkeys[new_numberOfKeys++]; + + memcpy(outkey, cur, sizeof(ScanKeyData)); + if (numberOfEqualCols == attno - 1) + _bt_mark_scankey_required(outkey); if (j == (BTEqualStrategyNumber - 1)) hasOtherTypeEqual = true; continue; @@ -514,6 +498,73 @@ _bt_preprocess_keys(IndexScanDesc scan) scan->keys_are_unique = true; } +/* + * Mark a scankey as "required to continue the scan". + * + * Depending on the operator type, the key may be required for both scan + * directions or just one. Also, if the key is a row comparison header, + * we have to mark the appropriate subsidiary ScanKeys as required. In + * such cases, the first subsidiary key is required, but subsequent ones + * are required only as long as they correspond to successive index columns. + * Otherwise the row comparison ordering is different from the index ordering + * and so we can't stop the scan on the basis of those lower-order columns. + * + * Note: when we set required-key flag bits in a subsidiary scankey, we are + * scribbling on a data structure belonging to the index AM's caller, not on + * our private copy. This should be OK because the marking will not change + * from scan to scan within a query, and so we'd just re-mark the same way + * anyway on a rescan. Something to keep an eye on though. + */ +static void +_bt_mark_scankey_required(ScanKey skey) +{ + int addflags; + + switch (skey->sk_strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + addflags = SK_BT_REQFWD; + break; + case BTEqualStrategyNumber: + addflags = SK_BT_REQFWD | SK_BT_REQBKWD; + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + addflags = SK_BT_REQBKWD; + break; + default: + elog(ERROR, "unrecognized StrategyNumber: %d", + (int) skey->sk_strategy); + addflags = 0; /* keep compiler quiet */ + break; + } + + skey->sk_flags |= addflags; + + if (skey->sk_flags & SK_ROW_HEADER) + { + ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument); + AttrNumber attno = skey->sk_attno; + + /* First subkey should be same as the header says */ + Assert(subkey->sk_attno == attno); + + for (;;) + { + Assert(subkey->sk_flags & SK_ROW_MEMBER); + Assert(subkey->sk_strategy == skey->sk_strategy); + if (subkey->sk_attno != attno) + break; /* non-adjacent key, so not required */ + subkey->sk_flags |= addflags; + if (subkey->sk_flags & SK_ROW_END) + break; + subkey++; + attno++; + } + } +} + /* * Test whether an indextuple satisfies all the scankey conditions. * @@ -595,6 +646,14 @@ _bt_checkkeys(IndexScanDesc scan, bool isNull; Datum test; + /* row-comparison keys need special processing */ + if (key->sk_flags & SK_ROW_HEADER) + { + if (_bt_check_rowcompare(key, tuple, tupdesc, dir, continuescan)) + continue; + return false; + } + datum = index_getattr(tuple, key->sk_attno, tupdesc, @@ -660,3 +719,136 @@ _bt_checkkeys(IndexScanDesc scan, return tuple_valid; } + +/* + * Test whether an indextuple satisfies a row-comparison scan condition. + * + * Return true if so, false if not. If not, also clear *continuescan if + * it's not possible for any future tuples in the current scan direction + * to pass the qual. + * + * This is a subroutine for _bt_checkkeys, which see for more info. + */ +static bool +_bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc, + ScanDirection dir, bool *continuescan) +{ + ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument); + int32 cmpresult = 0; + bool result; + + /* First subkey should be same as the header says */ + Assert(subkey->sk_attno == skey->sk_attno); + + /* Loop over columns of the row condition */ + for (;;) + { + Datum datum; + bool isNull; + + Assert(subkey->sk_flags & SK_ROW_MEMBER); + Assert(subkey->sk_strategy == skey->sk_strategy); + + datum = index_getattr(tuple, + subkey->sk_attno, + tupdesc, + &isNull); + + if (isNull) + { + /* + * Since NULLs are sorted after non-NULLs, we know we have reached + * the upper limit of the range of values for this index attr. On + * a forward scan, we can stop if this qual is one of the "must + * match" subset. On a backward scan, however, we should keep + * going. + */ + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + + /* + * In any case, this indextuple doesn't match the qual. + */ + return false; + } + + if (subkey->sk_flags & SK_ISNULL) + { + /* + * Unlike the simple-scankey case, this isn't a disallowed case. + * But it can never match. If all the earlier row comparison + * columns are required for the scan direction, we can stop + * the scan, because there can't be another tuple that will + * succeed. + */ + if (subkey != (ScanKey) DatumGetPointer(skey->sk_argument)) + subkey--; + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + else if ((subkey->sk_flags & SK_BT_REQBKWD) && + ScanDirectionIsBackward(dir)) + *continuescan = false; + return false; + } + + /* Perform the test --- three-way comparison not bool operator */ + cmpresult = DatumGetInt32(FunctionCall2(&subkey->sk_func, + datum, + subkey->sk_argument)); + + /* Done comparing if unequal, else advance to next column */ + if (cmpresult != 0) + break; + + if (subkey->sk_flags & SK_ROW_END) + break; + subkey++; + } + + /* + * At this point cmpresult indicates the overall result of the row + * comparison, and subkey points to the deciding column (or the last + * column if the result is "="). + */ + switch (subkey->sk_strategy) + { + /* EQ and NE cases aren't allowed here */ + case BTLessStrategyNumber: + result = (cmpresult < 0); + break; + case BTLessEqualStrategyNumber: + result = (cmpresult <= 0); + break; + case BTGreaterEqualStrategyNumber: + result = (cmpresult >= 0); + break; + case BTGreaterStrategyNumber: + result = (cmpresult > 0); + break; + default: + elog(ERROR, "unrecognized RowCompareType: %d", + (int) subkey->sk_strategy); + result = 0; /* keep compiler quiet */ + break; + } + + if (!result) + { + /* + * Tuple fails this qual. If it's a required qual for the current + * scan direction, then we can conclude no further tuples will + * pass, either. Note we have to look at the deciding column, not + * necessarily the first or last column of the row condition. + */ + if ((subkey->sk_flags & SK_BT_REQFWD) && + ScanDirectionIsForward(dir)) + *continuescan = false; + else if ((subkey->sk_flags & SK_BT_REQBKWD) && + ScanDirectionIsBackward(dir)) + *continuescan = false; + } + + return result; +} diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c index 114de29ba8e0864d286a83e8839a57b900d74e6d..37839c0255b33660dc60387dfa92b21d60ade424 100644 --- a/src/backend/executor/nodeBitmapIndexscan.c +++ b/src/backend/executor/nodeBitmapIndexscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.14 2005/12/03 05:51:01 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeBitmapIndexscan.c,v 1.15 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -244,6 +244,20 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) #define BITMAPINDEXSCAN_NSLOTS 0 + /* + * We do not open or lock the base relation here. We assume that an + * ancestor BitmapHeapScan node is holding AccessShareLock (or better) + * on the heap relation throughout the execution of the plan tree. + */ + + indexstate->ss.ss_currentRelation = NULL; + indexstate->ss.ss_currentScanDesc = NULL; + + /* + * Open the index relation. + */ + indexstate->biss_RelationDesc = index_open(node->indexid); + /* * Initialize index-specific scan state */ @@ -255,6 +269,7 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) * build the index scan keys from the index qualification */ ExecIndexBuildScanKeys((PlanState *) indexstate, + indexstate->biss_RelationDesc, node->indexqual, node->indexstrategy, node->indexsubtype, @@ -286,16 +301,8 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) } /* - * We do not open or lock the base relation here. We assume that an - * ancestor BitmapHeapScan node is holding AccessShareLock (or better) - * on the heap relation throughout the execution of the plan tree. - */ - - indexstate->ss.ss_currentRelation = NULL; - indexstate->ss.ss_currentScanDesc = NULL; - - /* - * Open the index relation and initialize relation and scan descriptors. + * Initialize scan descriptor. + * * Note we acquire no locks here; the index machinery does its own locks * and unlocks. (We rely on having a lock on the parent table to * ensure the index won't go away!) Furthermore, if the parent table @@ -303,7 +310,6 @@ ExecInitBitmapIndexScan(BitmapIndexScan *node, EState *estate) * opened and write-locked the index, so we can tell the index machinery * not to bother getting an extra lock. */ - indexstate->biss_RelationDesc = index_open(node->indexid); relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid); indexstate->biss_ScanDesc = index_beginscan_multi(indexstate->biss_RelationDesc, diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index 94495b4bc711678383403430ba797bfe0b1aab1e..16406c784f45aca7485ac8ca5c224311811537f5 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.109 2005/12/03 05:51:02 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.110 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,6 +26,7 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/nbtree.h" #include "executor/execdebug.h" #include "executor/nodeIndexscan.h" #include "miscadmin.h" @@ -504,6 +505,24 @@ ExecInitIndexScan(IndexScan *node, EState *estate) ExecInitResultTupleSlot(estate, &indexstate->ss.ps); ExecInitScanTupleSlot(estate, &indexstate->ss); + /* + * open the base relation and acquire appropriate lock on it. + */ + currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid); + + indexstate->ss.ss_currentRelation = currentRelation; + indexstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */ + + /* + * get the scan type from the relation descriptor. + */ + ExecAssignScanType(&indexstate->ss, RelationGetDescr(currentRelation), false); + + /* + * Open the index relation. + */ + indexstate->iss_RelationDesc = index_open(node->indexid); + /* * Initialize index-specific scan state */ @@ -515,6 +534,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * build the index scan keys from the index qualification */ ExecIndexBuildScanKeys((PlanState *) indexstate, + indexstate->iss_RelationDesc, node->indexqual, node->indexstrategy, node->indexsubtype, @@ -545,20 +565,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate) } /* - * open the base relation and acquire appropriate lock on it. - */ - currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid); - - indexstate->ss.ss_currentRelation = currentRelation; - indexstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */ - - /* - * get the scan type from the relation descriptor. - */ - ExecAssignScanType(&indexstate->ss, RelationGetDescr(currentRelation), false); - - /* - * Open the index relation and initialize relation and scan descriptors. + * Initialize scan descriptor. + * * Note we acquire no locks here; the index machinery does its own locks * and unlocks. (We rely on having a lock on the parent table to * ensure the index won't go away!) Furthermore, if the parent table @@ -566,7 +574,6 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * opened and write-locked the index, so we can tell the index machinery * not to bother getting an extra lock. */ - indexstate->iss_RelationDesc = index_open(node->indexid); relistarget = ExecRelationIsTargetRelation(estate, node->scan.scanrelid); indexstate->iss_ScanDesc = index_beginscan(currentRelation, indexstate->iss_RelationDesc, @@ -595,7 +602,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * The index quals are passed to the index AM in the form of a ScanKey array. * This routine sets up the ScanKeys, fills in all constant fields of the * ScanKeys, and prepares information about the keys that have non-constant - * comparison values. We divide index qual expressions into three types: + * comparison values. We divide index qual expressions into four types: * * 1. Simple operator with constant comparison value ("indexkey op constant"). * For these, we just fill in a ScanKey containing the constant value. @@ -605,7 +612,12 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * expression value, and set up an IndexRuntimeKeyInfo struct to drive * evaluation of the expression at the right times. * - * 3. ScalarArrayOpExpr ("indexkey op ANY (array-expression)"). For these, + * 3. RowCompareExpr ("(indexkey, indexkey, ...) op (expr, expr, ...)"). + * For these, we create a header ScanKey plus a subsidiary ScanKey array, + * as specified in access/skey.h. The elements of the row comparison + * can have either constant or non-constant comparison values. + * + * 4. ScalarArrayOpExpr ("indexkey op ANY (array-expression)"). For these, * we create a ScanKey with everything filled in except the comparison value, * and set up an IndexArrayKeyInfo struct to drive processing of the qual. * (Note that we treat all array-expressions as requiring runtime evaluation, @@ -614,10 +626,15 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * Input params are: * * planstate: executor state node we are working for + * index: the index we are building scan keys for * quals: indexquals expressions * strategies: associated operator strategy numbers * subtypes: associated operator subtype OIDs * + * (Any elements of the strategies and subtypes lists that correspond to + * RowCompareExpr quals are not used here; instead we look up the info + * afresh.) + * * Output params are: * * *scanKeys: receives ptr to array of ScanKeys @@ -631,8 +648,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate) * ScalarArrayOpExpr quals are not supported. */ void -ExecIndexBuildScanKeys(PlanState *planstate, List *quals, - List *strategies, List *subtypes, +ExecIndexBuildScanKeys(PlanState *planstate, Relation index, + List *quals, List *strategies, List *subtypes, ScanKey *scanKeys, int *numScanKeys, IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys, IndexArrayKeyInfo **arrayKeys, int *numArrayKeys) @@ -644,20 +661,42 @@ ExecIndexBuildScanKeys(PlanState *planstate, List *quals, IndexRuntimeKeyInfo *runtime_keys; IndexArrayKeyInfo *array_keys; int n_scan_keys; + int extra_scan_keys; int n_runtime_keys; int n_array_keys; int j; + /* + * If there are any RowCompareExpr quals, we need extra ScanKey entries + * for them, and possibly extra runtime-key entries. Count up what's + * needed. (The subsidiary ScanKey arrays for the RowCompareExprs could + * be allocated as separate chunks, but we have to count anyway to make + * runtime_keys large enough, so might as well just do one palloc.) + */ n_scan_keys = list_length(quals); - scan_keys = (ScanKey) palloc(n_scan_keys * sizeof(ScanKeyData)); + extra_scan_keys = 0; + foreach(qual_cell, quals) + { + if (IsA(lfirst(qual_cell), RowCompareExpr)) + extra_scan_keys += + list_length(((RowCompareExpr *) lfirst(qual_cell))->opnos); + } + scan_keys = (ScanKey) + palloc((n_scan_keys + extra_scan_keys) * sizeof(ScanKeyData)); /* Allocate these arrays as large as they could possibly need to be */ runtime_keys = (IndexRuntimeKeyInfo *) - palloc(n_scan_keys * sizeof(IndexRuntimeKeyInfo)); + palloc((n_scan_keys + extra_scan_keys) * sizeof(IndexRuntimeKeyInfo)); array_keys = (IndexArrayKeyInfo *) palloc0(n_scan_keys * sizeof(IndexArrayKeyInfo)); n_runtime_keys = 0; n_array_keys = 0; + /* + * Below here, extra_scan_keys is index of first cell to use for next + * RowCompareExpr + */ + extra_scan_keys = n_scan_keys; + /* * for each opclause in the given qual, convert each qual's opclause into * a single scan key @@ -749,6 +788,119 @@ ExecIndexBuildScanKeys(PlanState *planstate, List *quals, opfuncid, /* reg proc to use */ scanvalue); /* constant */ } + else if (IsA(clause, RowCompareExpr)) + { + /* (indexkey, indexkey, ...) op (expression, expression, ...) */ + RowCompareExpr *rc = (RowCompareExpr *) clause; + ListCell *largs_cell = list_head(rc->largs); + ListCell *rargs_cell = list_head(rc->rargs); + ListCell *opnos_cell = list_head(rc->opnos); + ScanKey first_sub_key = &scan_keys[extra_scan_keys]; + + /* Scan RowCompare columns and generate subsidiary ScanKey items */ + while (opnos_cell != NULL) + { + ScanKey this_sub_key = &scan_keys[extra_scan_keys]; + int flags = SK_ROW_MEMBER; + Datum scanvalue; + Oid opno; + Oid opclass; + int op_strategy; + Oid op_subtype; + bool op_recheck; + + /* + * leftop should be the index key Var, possibly relabeled + */ + leftop = (Expr *) lfirst(largs_cell); + largs_cell = lnext(largs_cell); + + if (leftop && IsA(leftop, RelabelType)) + leftop = ((RelabelType *) leftop)->arg; + + Assert(leftop != NULL); + + if (!(IsA(leftop, Var) && + var_is_rel((Var *) leftop))) + elog(ERROR, "indexqual doesn't have key on left side"); + + varattno = ((Var *) leftop)->varattno; + + /* + * rightop is the constant or variable comparison value + */ + rightop = (Expr *) lfirst(rargs_cell); + rargs_cell = lnext(rargs_cell); + + if (rightop && IsA(rightop, RelabelType)) + rightop = ((RelabelType *) rightop)->arg; + + Assert(rightop != NULL); + + if (IsA(rightop, Const)) + { + /* OK, simple constant comparison value */ + scanvalue = ((Const *) rightop)->constvalue; + if (((Const *) rightop)->constisnull) + flags |= SK_ISNULL; + } + else + { + /* Need to treat this one as a runtime key */ + runtime_keys[n_runtime_keys].scan_key = this_sub_key; + runtime_keys[n_runtime_keys].key_expr = + ExecInitExpr(rightop, planstate); + n_runtime_keys++; + scanvalue = (Datum) 0; + } + + /* + * We have to look up the operator's associated btree support + * function + */ + opno = lfirst_oid(opnos_cell); + opnos_cell = lnext(opnos_cell); + + if (index->rd_rel->relam != BTREE_AM_OID || + varattno < 1 || varattno > index->rd_index->indnatts) + elog(ERROR, "bogus RowCompare index qualification"); + opclass = index->rd_indclass->values[varattno - 1]; + + get_op_opclass_properties(opno, opclass, + &op_strategy, &op_subtype, &op_recheck); + + if (op_strategy != rc->rctype) + elog(ERROR, "RowCompare index qualification contains wrong operator"); + + opfuncid = get_opclass_proc(opclass, op_subtype, BTORDER_PROC); + + /* + * initialize the subsidiary scan key's fields appropriately + */ + ScanKeyEntryInitialize(this_sub_key, + flags, + varattno, /* attribute number */ + op_strategy, /* op's strategy */ + op_subtype, /* strategy subtype */ + opfuncid, /* reg proc to use */ + scanvalue); /* constant */ + extra_scan_keys++; + } + + /* Mark the last subsidiary scankey correctly */ + scan_keys[extra_scan_keys - 1].sk_flags |= SK_ROW_END; + + /* + * We don't use ScanKeyEntryInitialize for the header because + * it isn't going to contain a valid sk_func pointer. + */ + MemSet(this_scan_key, 0, sizeof(ScanKeyData)); + this_scan_key->sk_flags = SK_ROW_HEADER; + this_scan_key->sk_attno = first_sub_key->sk_attno; + this_scan_key->sk_strategy = rc->rctype; + /* sk_subtype, sk_func not used in a header */ + this_scan_key->sk_argument = PointerGetDatum(first_sub_key); + } else if (IsA(clause, ScalarArrayOpExpr)) { /* indexkey op ANY (array-expression) */ diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c index 159960764fb4693d4df7b93ac8c5ae271ff54d48..9ec5911403f2ffbac1245ab896066975a465c800 100644 --- a/src/backend/optimizer/path/indxpath.c +++ b/src/backend/optimizer/path/indxpath.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.196 2005/12/06 16:50:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/indxpath.c,v 1.197 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -61,6 +61,11 @@ static bool match_clause_to_indexcol(IndexOptInfo *index, SaOpControl saop_control); static bool is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left); +static bool match_rowcompare_to_indexcol(IndexOptInfo *index, + int indexcol, + Oid opclass, + RowCompareExpr *clause, + Relids outer_relids); static Relids indexable_outerrelids(RelOptInfo *rel); static bool matches_any_index(RestrictInfo *rinfo, RelOptInfo *rel, Relids outer_relids); @@ -82,7 +87,10 @@ static bool match_special_index_operator(Expr *clause, Oid opclass, bool indexkey_on_left); static Expr *expand_boolean_index_clause(Node *clause, int indexcol, IndexOptInfo *index); -static List *expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass); +static List *expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass); +static RestrictInfo *expand_indexqual_rowcompare(RestrictInfo *rinfo, + IndexOptInfo *index, + int indexcol); static List *prefix_quals(Node *leftop, Oid opclass, Const *prefix, Pattern_Prefix_Status pstatus); static List *network_prefix_quals(Node *leftop, Oid expr_op, Oid opclass, @@ -900,6 +908,14 @@ group_clauses_by_indexkey(IndexOptInfo *index, * We do not actually do the commuting here, but we check whether a * suitable commutator operator is available. * + * It is also possible to match RowCompareExpr clauses to indexes (but + * currently, only btree indexes handle this). In this routine we will + * report a match if the first column of the row comparison matches the + * target index column. This is sufficient to guarantee that some index + * condition can be constructed from the RowCompareExpr --- whether the + * remaining columns match the index too is considered in + * expand_indexqual_rowcompare(). + * * It is also possible to match ScalarArrayOpExpr clauses to indexes, when * the clause is of the form "indexkey op ANY (arrayconst)". Since the * executor can only handle these in the context of bitmap index scans, @@ -944,7 +960,8 @@ match_clause_to_indexcol(IndexOptInfo *index, /* * Clause must be a binary opclause, or possibly a ScalarArrayOpExpr - * (which is always binary, by definition). + * (which is always binary, by definition). Or it could be a + * RowCompareExpr, which we pass off to match_rowcompare_to_indexcol(). */ if (is_opclause(clause)) { @@ -972,6 +989,12 @@ match_clause_to_indexcol(IndexOptInfo *index, expr_op = saop->opno; plain_op = false; } + else if (clause && IsA(clause, RowCompareExpr)) + { + return match_rowcompare_to_indexcol(index, indexcol, opclass, + (RowCompareExpr *) clause, + outer_relids); + } else return false; @@ -1039,6 +1062,74 @@ is_indexable_operator(Oid expr_op, Oid opclass, bool indexkey_on_left) return op_in_opclass(expr_op, opclass); } +/* + * match_rowcompare_to_indexcol() + * Handles the RowCompareExpr case for match_clause_to_indexcol(), + * which see for comments. + */ +static bool +match_rowcompare_to_indexcol(IndexOptInfo *index, + int indexcol, + Oid opclass, + RowCompareExpr *clause, + Relids outer_relids) +{ + Node *leftop, + *rightop; + Oid expr_op; + + /* Forget it if we're not dealing with a btree index */ + if (index->relam != BTREE_AM_OID) + return false; + + /* + * We could do the matching on the basis of insisting that the opclass + * shown in the RowCompareExpr be the same as the index column's opclass, + * but that does not work well for cross-type comparisons (the opclass + * could be for the other datatype). Also it would fail to handle indexes + * using reverse-sort opclasses. Instead, match if the operator listed in + * the RowCompareExpr is the < <= > or >= member of the index opclass + * (after commutation, if the indexkey is on the right). + */ + leftop = (Node *) linitial(clause->largs); + rightop = (Node *) linitial(clause->rargs); + expr_op = linitial_oid(clause->opnos); + + /* + * These syntactic tests are the same as in match_clause_to_indexcol() + */ + if (match_index_to_operand(leftop, indexcol, index) && + bms_is_subset(pull_varnos(rightop), outer_relids) && + !contain_volatile_functions(rightop)) + { + /* OK, indexkey is on left */ + } + else if (match_index_to_operand(rightop, indexcol, index) && + bms_is_subset(pull_varnos(leftop), outer_relids) && + !contain_volatile_functions(leftop)) + { + /* indexkey is on right, so commute the operator */ + expr_op = get_commutator(expr_op); + if (expr_op == InvalidOid) + return false; + } + else + return false; + + /* We're good if the operator is the right type of opclass member */ + switch (get_op_opclass_strategy(expr_op, opclass)) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + return true; + } + + return false; +} + + /**************************************************************************** * ---- ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS ---- ****************************************************************************/ @@ -2014,7 +2105,8 @@ match_special_index_operator(Expr *clause, Oid opclass, * of index qual clauses. Standard qual clauses (those in the index's * opclass) are passed through unchanged. Boolean clauses and "special" * index operators are expanded into clauses that the indexscan machinery - * will know what to do with. + * will know what to do with. RowCompare clauses are simplified if + * necessary to create a clause that is fully checkable by the index. * * The input list is ordered by index key, and so the output list is too. * (The latter is not depended on by any part of the core planner, I believe, @@ -2041,13 +2133,14 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) foreach(l, (List *) lfirst(clausegroup_item)) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(l); + Expr *clause = rinfo->clause; /* First check for boolean cases */ if (IsBooleanOpclass(curClass)) { Expr *boolqual; - boolqual = expand_boolean_index_clause((Node *) rinfo->clause, + boolqual = expand_boolean_index_clause((Node *) clause, indexcol, index); if (boolqual) @@ -2061,16 +2154,31 @@ expand_indexqual_conditions(IndexOptInfo *index, List *clausegroups) } } - /* Next check for ScalarArrayOp cases */ - if (IsA(rinfo->clause, ScalarArrayOpExpr)) + /* + * Else it must be an opclause (usual case), ScalarArrayOp, or + * RowCompare + */ + if (is_opclause(clause)) { + resultquals = list_concat(resultquals, + expand_indexqual_opclause(rinfo, + curClass)); + } + else if (IsA(clause, ScalarArrayOpExpr)) + { + /* no extra work at this time */ resultquals = lappend(resultquals, rinfo); - continue; } - - resultquals = list_concat(resultquals, - expand_indexqual_condition(rinfo, - curClass)); + else if (IsA(clause, RowCompareExpr)) + { + resultquals = lappend(resultquals, + expand_indexqual_rowcompare(rinfo, + index, + indexcol)); + } + else + elog(ERROR, "unsupported indexqual type: %d", + (int) nodeTag(clause)); } clausegroup_item = lnext(clausegroup_item); @@ -2145,16 +2253,15 @@ expand_boolean_index_clause(Node *clause, } /* - * expand_indexqual_condition --- expand a single indexqual condition - * (other than a boolean-qual or ScalarArrayOp case) + * expand_indexqual_opclause --- expand a single indexqual condition + * that is an operator clause * * The input is a single RestrictInfo, the output a list of RestrictInfos */ static List * -expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass) +expand_indexqual_opclause(RestrictInfo *rinfo, Oid opclass) { Expr *clause = rinfo->clause; - /* we know these will succeed */ Node *leftop = get_leftop(clause); Node *rightop = get_rightop(clause); @@ -2224,6 +2331,204 @@ expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass) return result; } +/* + * expand_indexqual_rowcompare --- expand a single indexqual condition + * that is a RowCompareExpr + * + * It's already known that the first column of the row comparison matches + * the specified column of the index. We can use additional columns of the + * row comparison as index qualifications, so long as they match the index + * in the "same direction", ie, the indexkeys are all on the same side of the + * clause and the operators are all the same-type members of the opclasses. + * If all the columns of the RowCompareExpr match in this way, we just use it + * as-is. Otherwise, we build a shortened RowCompareExpr (if more than one + * column matches) or a simple OpExpr (if the first-column match is all + * there is). In these cases the modified clause is always "<=" or ">=" + * even when the original was "<" or ">" --- this is necessary to match all + * the rows that could match the original. (We are essentially building a + * lossy version of the row comparison when we do this.) + */ +static RestrictInfo * +expand_indexqual_rowcompare(RestrictInfo *rinfo, + IndexOptInfo *index, + int indexcol) +{ + RowCompareExpr *clause = (RowCompareExpr *) rinfo->clause; + bool var_on_left; + int op_strategy; + Oid op_subtype; + bool op_recheck; + int matching_cols; + Oid expr_op; + List *opclasses; + List *subtypes; + List *new_ops; + ListCell *largs_cell; + ListCell *rargs_cell; + ListCell *opnos_cell; + + /* We have to figure out (again) how the first col matches */ + var_on_left = match_index_to_operand((Node *) linitial(clause->largs), + indexcol, index); + Assert(var_on_left || + match_index_to_operand((Node *) linitial(clause->rargs), + indexcol, index)); + expr_op = linitial_oid(clause->opnos); + if (!var_on_left) + expr_op = get_commutator(expr_op); + get_op_opclass_properties(expr_op, index->classlist[indexcol], + &op_strategy, &op_subtype, &op_recheck); + /* Build lists of the opclasses and operator subtypes in case needed */ + opclasses = list_make1_oid(index->classlist[indexcol]); + subtypes = list_make1_oid(op_subtype); + + /* + * See how many of the remaining columns match some index column + * in the same way. A note about rel membership tests: we assume + * that the clause as a whole is already known to use only Vars from + * the indexed relation and possibly some acceptable outer relations. + * So the "other" side of any potential index condition is OK as long + * as it doesn't use Vars from the indexed relation. + */ + matching_cols = 1; + largs_cell = lnext(list_head(clause->largs)); + rargs_cell = lnext(list_head(clause->rargs)); + opnos_cell = lnext(list_head(clause->opnos)); + + while (largs_cell != NULL) + { + Node *varop; + Node *constop; + int i; + + expr_op = lfirst_oid(opnos_cell); + if (var_on_left) + { + varop = (Node *) lfirst(largs_cell); + constop = (Node *) lfirst(rargs_cell); + } + else + { + varop = (Node *) lfirst(rargs_cell); + constop = (Node *) lfirst(largs_cell); + /* indexkey is on right, so commute the operator */ + expr_op = get_commutator(expr_op); + if (expr_op == InvalidOid) + break; /* operator is not usable */ + } + if (bms_is_member(index->rel->relid, pull_varnos(constop))) + break; /* no good, Var on wrong side */ + if (contain_volatile_functions(constop)) + break; /* no good, volatile comparison value */ + + /* + * The Var side can match any column of the index. If the user + * does something weird like having multiple identical index + * columns, we insist the match be on the first such column, + * to avoid confusing the executor. + */ + for (i = 0; i < index->ncolumns; i++) + { + if (match_index_to_operand(varop, i, index)) + break; + } + if (i >= index->ncolumns) + break; /* no match found */ + + /* Now, do we have the right operator for this column? */ + if (get_op_opclass_strategy(expr_op, index->classlist[i]) + != op_strategy) + break; + + /* Add opclass and subtype to lists */ + get_op_opclass_properties(expr_op, index->classlist[i], + &op_strategy, &op_subtype, &op_recheck); + opclasses = lappend_oid(opclasses, index->classlist[i]); + subtypes = lappend_oid(subtypes, op_subtype); + + /* This column matches, keep scanning */ + matching_cols++; + largs_cell = lnext(largs_cell); + rargs_cell = lnext(rargs_cell); + opnos_cell = lnext(opnos_cell); + } + + /* Return clause as-is if it's all usable as index quals */ + if (matching_cols == list_length(clause->opnos)) + return rinfo; + + /* + * We have to generate a subset rowcompare (possibly just one OpExpr). + * The painful part of this is changing < to <= or > to >=, so deal with + * that first. + */ + if (op_strategy == BTLessEqualStrategyNumber || + op_strategy == BTGreaterEqualStrategyNumber) + { + /* easy, just use the same operators */ + new_ops = list_truncate(list_copy(clause->opnos), matching_cols); + } + else + { + ListCell *opclasses_cell; + ListCell *subtypes_cell; + + if (op_strategy == BTLessStrategyNumber) + op_strategy = BTLessEqualStrategyNumber; + else if (op_strategy == BTGreaterStrategyNumber) + op_strategy = BTGreaterEqualStrategyNumber; + else + elog(ERROR, "unexpected strategy number %d", op_strategy); + new_ops = NIL; + forboth(opclasses_cell, opclasses, subtypes_cell, subtypes) + { + expr_op = get_opclass_member(lfirst_oid(opclasses_cell), + lfirst_oid(subtypes_cell), + op_strategy); + if (!OidIsValid(expr_op)) /* should not happen */ + elog(ERROR, "could not find member %d of opclass %u", + op_strategy, lfirst_oid(opclasses_cell)); + if (!var_on_left) + { + expr_op = get_commutator(expr_op); + if (!OidIsValid(expr_op)) /* should not happen */ + elog(ERROR, "could not find commutator of member %d of opclass %u", + op_strategy, lfirst_oid(opclasses_cell)); + } + new_ops = lappend_oid(new_ops, expr_op); + } + } + + /* If we have more than one matching col, create a subset rowcompare */ + if (matching_cols > 1) + { + RowCompareExpr *rc = makeNode(RowCompareExpr); + + if (var_on_left) + rc->rctype = (RowCompareType) op_strategy; + else + rc->rctype = (op_strategy == BTLessEqualStrategyNumber) ? + ROWCOMPARE_GE : ROWCOMPARE_LE; + rc->opnos = new_ops; + rc->opclasses = list_truncate(list_copy(clause->opclasses), + matching_cols); + rc->largs = list_truncate((List *) copyObject(clause->largs), + matching_cols); + rc->rargs = list_truncate((List *) copyObject(clause->rargs), + matching_cols); + return make_restrictinfo((Expr *) rc, true, false, NULL); + } + else + { + Expr *opexpr; + + opexpr = make_opclause(linitial_oid(new_ops), BOOLOID, false, + copyObject(linitial(clause->largs)), + copyObject(linitial(clause->rargs))); + return make_restrictinfo(opexpr, true, false, NULL); + } +} + /* * Given a fixed prefix that all the "leftop" values must have, * generate suitable indexqual condition(s). opclass is the index diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 4acac8421c863ccf2c23e4ce851b4c58f97d3447..e5355340c17ef6da7fc751712772e4d1240d5126 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.205 2005/11/26 22:14:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.206 2006/01/25 20:29:23 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1583,7 +1583,7 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, * (only) the base relation. */ if (!bms_equal(rinfo->left_relids, index->rel->relids)) - CommuteClause(op); + CommuteOpExpr(op); /* * Now, determine which index attribute this is, change the @@ -1594,6 +1594,41 @@ fix_indexqual_references(List *indexquals, IndexPath *index_path, &opclass); clause_op = op->opno; } + else if (IsA(clause, RowCompareExpr)) + { + RowCompareExpr *rc = (RowCompareExpr *) clause; + ListCell *lc; + + /* + * Check to see if the indexkey is on the right; if so, commute + * the clause. The indexkey should be the side that refers to + * (only) the base relation. + */ + if (!bms_overlap(pull_varnos(linitial(rc->largs)), + index->rel->relids)) + CommuteRowCompareExpr(rc); + + /* + * For each column in the row comparison, determine which index + * attribute this is and change the indexkey operand as needed. + * + * Save the index opclass for only the first column. We will + * return the operator and opclass info for just the first + * column of the row comparison; the executor will have to + * look up the rest if it needs them. + */ + foreach(lc, rc->largs) + { + Oid tmp_opclass; + + lfirst(lc) = fix_indexqual_operand(lfirst(lc), + index, + &tmp_opclass); + if (lc == list_head(rc->largs)) + opclass = tmp_opclass; + } + clause_op = linitial_oid(rc->opnos); + } else if (IsA(clause, ScalarArrayOpExpr)) { ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; @@ -1745,7 +1780,7 @@ get_switched_clauses(List *clauses, Relids outerrelids) temp->opretset = clause->opretset; temp->args = list_copy(clause->args); /* Commute it --- note this modifies the temp node in-place. */ - CommuteClause(temp); + CommuteOpExpr(temp); t_list = lappend(t_list, temp); } else diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 2b6583c1dad08b5c85004a00db9bc2d0a1ee7f84..5266ff85d82ec9628929fa742b9c336cc1ba4e3f 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.205 2005/12/28 01:30:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.206 2006/01/25 20:29:23 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1167,12 +1167,12 @@ NumRelids(Node *clause) } /* - * CommuteClause: commute a binary operator clause + * CommuteOpExpr: commute a binary operator clause * * XXX the clause is destructively modified! */ void -CommuteClause(OpExpr *clause) +CommuteOpExpr(OpExpr *clause) { Oid opoid; Node *temp; @@ -1200,6 +1200,73 @@ CommuteClause(OpExpr *clause) lsecond(clause->args) = temp; } +/* + * CommuteRowCompareExpr: commute a RowCompareExpr clause + * + * XXX the clause is destructively modified! + */ +void +CommuteRowCompareExpr(RowCompareExpr *clause) +{ + List *newops; + List *temp; + ListCell *l; + + /* Sanity checks: caller is at fault if these fail */ + if (!IsA(clause, RowCompareExpr)) + elog(ERROR, "expected a RowCompareExpr"); + + /* Build list of commuted operators */ + newops = NIL; + foreach(l, clause->opnos) + { + Oid opoid = lfirst_oid(l); + + opoid = get_commutator(opoid); + if (!OidIsValid(opoid)) + elog(ERROR, "could not find commutator for operator %u", + lfirst_oid(l)); + newops = lappend_oid(newops, opoid); + } + + /* + * modify the clause in-place! + */ + switch (clause->rctype) + { + case ROWCOMPARE_LT: + clause->rctype = ROWCOMPARE_GT; + break; + case ROWCOMPARE_LE: + clause->rctype = ROWCOMPARE_GE; + break; + case ROWCOMPARE_GE: + clause->rctype = ROWCOMPARE_LE; + break; + case ROWCOMPARE_GT: + clause->rctype = ROWCOMPARE_LT; + break; + default: + elog(ERROR, "unexpected RowCompare type: %d", + (int) clause->rctype); + break; + } + + clause->opnos = newops; + /* + * Note: we don't bother to update the opclasses list, but just set + * it to empty. This is OK since this routine is currently only used + * for index quals, and the index machinery won't use the opclass + * information. The original opclass list is NOT valid if we have + * commuted any cross-type comparisons, so don't leave it in place. + */ + clause->opclasses = NIL; /* XXX */ + + temp = clause->largs; + clause->largs = clause->rargs; + clause->rargs = temp; +} + /* * strip_implicit_coercions: remove implicit coercions at top level of tree * diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 336c1deaeaa8ddb3f2c3b1354bc56e887301f91d..cb9acf2d8a3edb083c30545e221903ce82c29128 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.196 2006/01/14 00:14:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/selfuncs.c,v 1.197 2006/01/25 20:29:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -4657,6 +4657,9 @@ btcostestimate(PG_FUNCTION_ARGS) * to find out which ones count as boundary quals. We rely on the * knowledge that they are given in index column order. * + * For a RowCompareExpr, we consider only the first column, just as + * rowcomparesel() does. + * * If there's a ScalarArrayOpExpr in the quals, we'll actually perform * N index scans not one, but the ScalarArrayOpExpr's operator can be * considered to act the same as it normally does. @@ -4682,6 +4685,14 @@ btcostestimate(PG_FUNCTION_ARGS) rightop = get_rightop(clause); clause_op = ((OpExpr *) clause)->opno; } + else if (IsA(clause, RowCompareExpr)) + { + RowCompareExpr *rc = (RowCompareExpr *) clause; + + leftop = (Node *) linitial(rc->largs); + rightop = (Node *) linitial(rc->rargs); + clause_op = linitial_oid(rc->opnos); + } else if (IsA(clause, ScalarArrayOpExpr)) { ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause; diff --git a/src/include/access/skey.h b/src/include/access/skey.h index f3845e55184e3495fc8e7f8c2bfeb2ca3709eaf5..ecca1b84cff3bb289fe610e4b19a20c83569fb49 100644 --- a/src/include/access/skey.h +++ b/src/include/access/skey.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/skey.h,v 1.30 2006/01/14 22:03:35 tgl Exp $ + * $PostgreSQL: pgsql/src/include/access/skey.h,v 1.31 2006/01/25 20:29:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -69,6 +69,36 @@ typedef struct ScanKeyData typedef ScanKeyData *ScanKey; +/* + * About row comparisons: + * + * The ScanKey data structure also supports row comparisons, that is ordered + * tuple comparisons like (x, y) > (c1, c2), having the SQL-spec semantics + * "x > c1 OR (x = c1 AND y > c2)". Note that this is currently only + * implemented for btree index searches, not for heapscans or any other index + * type. A row comparison is represented by a "header" ScanKey entry plus + * a separate array of ScanKeys, one for each column of the row comparison. + * The header entry has these properties: + * sk_flags = SK_ROW_HEADER + * sk_attno = index column number for leading column of row comparison + * sk_strategy = btree strategy code for semantics of row comparison + * (ie, < <= > or >=) + * sk_subtype, sk_func: not used + * sk_argument: pointer to subsidiary ScanKey array + * If the header is part of a ScanKey array that's sorted by attno, it + * must be sorted according to the leading column number. + * + * The subsidiary ScanKey array appears in logical column order of the row + * comparison, which may be different from index column order. The array + * elements are like a normal ScanKey array except that: + * sk_flags must include SK_ROW_MEMBER, plus SK_ROW_END in the last + * element (needed since row header does not include a count) + * sk_func points to the btree comparison support function for the + * opclass, NOT the operator's implementation function. + * sk_strategy must be the same in all elements of the subsidiary array, + * that is, the same as in the header entry. + */ + /* * ScanKeyData sk_flags * @@ -78,6 +108,9 @@ typedef ScanKeyData *ScanKey; */ #define SK_ISNULL 0x0001 /* sk_argument is NULL */ #define SK_UNARY 0x0002 /* unary operator (currently unsupported) */ +#define SK_ROW_HEADER 0x0004 /* row comparison header (see above) */ +#define SK_ROW_MEMBER 0x0008 /* row comparison member (see above) */ +#define SK_ROW_END 0x0010 /* last row comparison member (see above) */ /* diff --git a/src/include/executor/nodeIndexscan.h b/src/include/executor/nodeIndexscan.h index 21bb254f63e898501092593216b025a39ecbf103..d36defaa0161c17bee01601dec0c14118bb6566a 100644 --- a/src/include/executor/nodeIndexscan.h +++ b/src/include/executor/nodeIndexscan.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/nodeIndexscan.h,v 1.25 2005/11/25 19:47:50 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/nodeIndexscan.h,v 1.26 2006/01/25 20:29:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,8 +25,8 @@ extern void ExecIndexRestrPos(IndexScanState *node); extern void ExecIndexReScan(IndexScanState *node, ExprContext *exprCtxt); /* routines exported to share code with nodeBitmapIndexscan.c */ -extern void ExecIndexBuildScanKeys(PlanState *planstate, List *quals, - List *strategies, List *subtypes, +extern void ExecIndexBuildScanKeys(PlanState *planstate, Relation index, + List *quals, List *strategies, List *subtypes, ScanKey *scanKeys, int *numScanKeys, IndexRuntimeKeyInfo **runtimeKeys, int *numRuntimeKeys, IndexArrayKeyInfo **arrayKeys, int *numArrayKeys); diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 0d3770dc5c4f438097e0a73e6f9dc9f6fb4a3c12..11eb6417fa7253c8a8c73fb3004c025c954cf458 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.81 2005/12/20 02:30:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/clauses.h,v 1.82 2006/01/25 20:29:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -67,7 +67,9 @@ extern bool has_distinct_clause(Query *query); extern bool has_distinct_on_clause(Query *query); extern int NumRelids(Node *clause); -extern void CommuteClause(OpExpr *clause); + +extern void CommuteOpExpr(OpExpr *clause); +extern void CommuteRowCompareExpr(RowCompareExpr *clause); extern Node *strip_implicit_coercions(Node *node);