Allow row comparisons to be used as indexscan qualifications.
authorTom Lane
Wed, 25 Jan 2006 20:29:24 +0000 (20:29 +0000)
committerTom Lane
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 8805d32de9b0d19a0f0ba29e968b0eaf93ea230e..618b643d7e00da7ffcc9ed115055ecd969fbeb99 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 b715383871a98d2fc1847247a466799559877668..90f2f64c81f51dc6c87824199fc477f635988d6b 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;
@@ -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;
+}
index 114de29ba8e0864d286a83e8839a57b900d74e6d..37839c0255b33660dc60387dfa92b21d60ade424 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -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,
index 94495b4bc711678383403430ba797bfe0b1aab1e..16406c784f45aca7485ac8ca5c224311811537f5 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"
@@ -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) */
index 159960764fb4693d4df7b93ac8c5ae271ff54d48..9ec5911403f2ffbac1245ab896066975a465c800 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);
@@ -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
index 4acac8421c863ccf2c23e4ce851b4c58f97d3447..e5355340c17ef6da7fc751712772e4d1240d5126 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 2b6583c1dad08b5c85004a00db9bc2d0a1ee7f84..5266ff85d82ec9628929fa742b9c336cc1ba4e3f 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;
@@ -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
  *
index 336c1deaeaa8ddb3f2c3b1354bc56e887301f91d..cb9acf2d8a3edb083c30545e221903ce82c29128 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 f3845e55184e3495fc8e7f8c2bfeb2ca3709eaf5..ecca1b84cff3bb289fe610e4b19a20c83569fb49 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -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) */
 
 
 /*
index 21bb254f63e898501092593216b025a39ecbf103..d36defaa0161c17bee01601dec0c14118bb6566a 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 0d3770dc5c4f438097e0a73e6f9dc9f6fb4a3c12..11eb6417fa7253c8a8c73fb3004c025c954cf458 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);