OSDN Git Service

Allow row comparisons to be used as indexscan qualifications.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 25 Jan 2006 20:29:24 +0000 (20:29 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 25 Jan 2006 20:29:24 +0000 (20:29 +0000)
This completes the project to upgrade our handling of row comparisons.

src/backend/access/nbtree/nbtsearch.c
src/backend/access/nbtree/nbtutils.c
src/backend/executor/nodeBitmapIndexscan.c
src/backend/executor/nodeIndexscan.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/util/clauses.c
src/backend/utils/adt/selfuncs.c
src/include/access/skey.h
src/include/executor/nodeIndexscan.h
src/include/optimizer/clauses.h

index 8805d32..618b643 100644 (file)
@@ -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);
+                       }
                }
        }
 
index b715383..90f2f64 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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;
+}
index 114de29..37839c0 100644 (file)
@@ -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,
index 94495b4..16406c7 100644 (file)
@@ -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) */
index 1599607..9ec5911 100644 (file)
@@ -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
index 4acac84..e535534 100644 (file)
@@ -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
index 2b6583c..5266ff8 100644 (file)
@@ -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
index 336c1de..cb9acf2 100644 (file)
@@ -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;
index f3845e5..ecca1b8 100644 (file)
@@ -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) */
 
 
 /*
index 21bb254..d36defa 100644 (file)
@@ -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);
index 0d3770d..11eb641 100644 (file)
@@ -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);