aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2006-01-25 20:29:24 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2006-01-25 20:29:24 +0000
commit3a0a16cb7e2aba8d9864d117d1199181432b42b8 (patch)
tree84965bad887c54100ebb9615257be6df2b18cee4 /src
parent06d45e485dcb65e948b747ccc3e995fadd183887 (diff)
downloadpostgresql-3a0a16cb7e2aba8d9864d117d1199181432b42b8.tar.gz
postgresql-3a0a16cb7e2aba8d9864d117d1199181432b42b8.zip
Allow row comparisons to be used as indexscan qualifications.
This completes the project to upgrade our handling of row comparisons.
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/nbtree/nbtsearch.c119
-rw-r--r--src/backend/access/nbtree/nbtutils.c282
-rw-r--r--src/backend/executor/nodeBitmapIndexscan.c30
-rw-r--r--src/backend/executor/nodeIndexscan.c196
-rw-r--r--src/backend/optimizer/path/indxpath.c337
-rw-r--r--src/backend/optimizer/plan/createplan.c41
-rw-r--r--src/backend/optimizer/util/clauses.c73
-rw-r--r--src/backend/utils/adt/selfuncs.c13
-rw-r--r--src/include/access/skey.h35
-rw-r--r--src/include/executor/nodeIndexscan.h6
-rw-r--r--src/include/optimizer/clauses.h6
11 files changed, 996 insertions, 142 deletions
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 8805d32de9b..618b643d7e0 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 b715383871a..90f2f64c81f 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;
@@ -515,6 +499,73 @@ _bt_preprocess_keys(IndexScanDesc scan)
}
/*
+ * 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.
*
* If so, copy its TID into scan->xs_ctup.t_self, and return TRUE.
@@ -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 114de29ba8e..37839c0255b 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 $
*
*-------------------------------------------------------------------------
*/
@@ -245,6 +245,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
*/
indexstate->biss_RuntimeKeysReady = false;
@@ -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 94495b4bc71..16406c784f4 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"
@@ -505,6 +506,24 @@ ExecInitIndexScan(IndexScan *node, EState *estate)
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
*/
indexstate->iss_RuntimeKeysReady = false;
@@ -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,21 +661,43 @@ 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 159960764fb..9ec5911403f 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);
@@ -2225,6 +2332,204 @@ expand_indexqual_condition(RestrictInfo *rinfo, Oid opclass)
}
/*
+ * 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
* operator class; we use it to deduce the appropriate comparison
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4acac8421c8..e5355340c17 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 2b6583c1dad..5266ff85d82 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;
@@ -1201,6 +1201,73 @@ CommuteClause(OpExpr *clause)
}
/*
+ * 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
*
* Note: there isn't any useful thing we can do with a RowExpr here, so
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 336c1deaeaa..cb9acf2d8a3 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 f3845e55184..ecca1b84cff 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 $
*
*-------------------------------------------------------------------------
*/
@@ -70,6 +70,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
*
* sk_flags bits 0-15 are reserved for system-wide use (symbols for those
@@ -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 21bb254f63e..d36defaa016 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 0d3770dc5c4..11eb6417fa7 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);