Commit 3a0a16cb authored by Tom Lane's avatar Tom Lane

Allow row comparisons to be used as indexscan qualifications.

This completes the project to upgrade our handling of row comparisons.
parent 06d45e48
......@@ -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);
}
}
}
......
......@@ -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;
}
......@@ -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,
......
......@@ -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) */
......
......@@ -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
......
......@@ -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
......
......@@ -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
*
......
......@@ -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;
......
......@@ -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) */
/*
......
......@@ -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);
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment