]> granicus.if.org Git - postgresql/commitdiff
Teach btree to handle ScalarArrayOpExpr quals natively.
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 16 Oct 2011 19:39:24 +0000 (15:39 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 16 Oct 2011 19:39:24 +0000 (15:39 -0400)
This allows "indexedcol op ANY(ARRAY[...])" conditions to be used in plain
indexscans, and particularly in index-only scans.

13 files changed:
doc/src/sgml/catalogs.sgml
src/backend/access/nbtree/nbtree.c
src/backend/access/nbtree/nbtutils.c
src/backend/executor/nodeIndexscan.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/path/indxpath.c
src/backend/optimizer/util/plancat.c
src/backend/utils/adt/selfuncs.c
src/include/access/nbtree.h
src/include/access/skey.h
src/include/catalog/catversion.h
src/include/catalog/pg_am.h
src/include/nodes/relation.h

index e830c5f3d44c8e3c5adb1163b7bd2d750f77a638..cfecaa6931a9352f909966e021555b92e984fc6f 100644 (file)
        for the first index column?</entry>
      </row>
 
+     <row>
+      <entry><structfield>amsearcharray</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>Does the access method support <literal>ScalarArrayOpExpr</> searches?</entry>
+     </row>
+
      <row>
       <entry><structfield>amsearchnulls</structfield></entry>
       <entry><type>bool</type></entry>
index 3401bc5bdb26b2be2314acaa28969c9510930980..60b7f599a74272df11f45a9f6f75ad119cce159d 100644 (file)
@@ -276,39 +276,63 @@ btgettuple(PG_FUNCTION_ARGS)
        scan->xs_recheck = false;
 
        /*
-        * If we've already initialized this scan, we can just advance it in the
-        * appropriate direction.  If we haven't done so yet, we call a routine to
-        * get the first item in the scan.
+        * If we have any array keys, initialize them during first call for a
+        * scan.  We can't do this in btrescan because we don't know the scan
+        * direction at that time.
         */
-       if (BTScanPosIsValid(so->currPos))
+       if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
+       {
+               /* punt if we have any unsatisfiable array keys */
+               if (so->numArrayKeys < 0)
+                       PG_RETURN_BOOL(false);
+
+               _bt_start_array_keys(scan, dir);
+       }
+
+       /* This loop handles advancing to the next array elements, if any */
+       do
        {
                /*
-                * Check to see if we should kill the previously-fetched tuple.
+                * If we've already initialized this scan, we can just advance it in
+                * the appropriate direction.  If we haven't done so yet, we call
+                * _bt_first() to get the first item in the scan.
                 */
-               if (scan->kill_prior_tuple)
+               if (!BTScanPosIsValid(so->currPos))
+                       res = _bt_first(scan, dir);
+               else
                {
                        /*
-                        * Yes, remember it for later.  (We'll deal with all such tuples
-                        * at once right before leaving the index page.)  The test for
-                        * numKilled overrun is not just paranoia: if the caller reverses
-                        * direction in the indexscan then the same item might get entered
-                        * multiple times.      It's not worth trying to optimize that, so we
-                        * don't detect it, but instead just forget any excess entries.
+                        * Check to see if we should kill the previously-fetched tuple.
                         */
-                       if (so->killedItems == NULL)
-                               so->killedItems = (int *)
-                                       palloc(MaxIndexTuplesPerPage * sizeof(int));
-                       if (so->numKilled < MaxIndexTuplesPerPage)
-                               so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+                       if (scan->kill_prior_tuple)
+                       {
+                               /*
+                                * Yes, remember it for later. (We'll deal with all such
+                                * tuples at once right before leaving the index page.)  The
+                                * test for numKilled overrun is not just paranoia: if the
+                                * caller reverses direction in the indexscan then the same
+                                * item might get entered multiple times. It's not worth
+                                * trying to optimize that, so we don't detect it, but instead
+                                * just forget any excess entries.
+                                */
+                               if (so->killedItems == NULL)
+                                       so->killedItems = (int *)
+                                               palloc(MaxIndexTuplesPerPage * sizeof(int));
+                               if (so->numKilled < MaxIndexTuplesPerPage)
+                                       so->killedItems[so->numKilled++] = so->currPos.itemIndex;
+                       }
+
+                       /*
+                        * Now continue the scan.
+                        */
+                       res = _bt_next(scan, dir);
                }
 
-               /*
-                * Now continue the scan.
-                */
-               res = _bt_next(scan, dir);
-       }
-       else
-               res = _bt_first(scan, dir);
+               /* If we have a tuple, return it ... */
+               if (res)
+                       break;
+               /* ... otherwise see if we have more array keys to deal with */
+       } while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
 
        PG_RETURN_BOOL(res);
 }
@@ -325,35 +349,50 @@ btgetbitmap(PG_FUNCTION_ARGS)
        int64           ntids = 0;
        ItemPointer heapTid;
 
-       /* Fetch the first page & tuple. */
-       if (!_bt_first(scan, ForwardScanDirection))
+       /*
+        * If we have any array keys, initialize them.
+        */
+       if (so->numArrayKeys)
        {
-               /* empty scan */
-               PG_RETURN_INT64(0);
+               /* punt if we have any unsatisfiable array keys */
+               if (so->numArrayKeys < 0)
+                       PG_RETURN_INT64(ntids);
+
+               _bt_start_array_keys(scan, ForwardScanDirection);
        }
-       /* Save tuple ID, and continue scanning */
-       heapTid = &scan->xs_ctup.t_self;
-       tbm_add_tuples(tbm, heapTid, 1, false);
-       ntids++;
 
-       for (;;)
+       /* This loop handles advancing to the next array elements, if any */
+       do
        {
-               /*
-                * Advance to next tuple within page.  This is the same as the easy
-                * case in _bt_next().
-                */
-               if (++so->currPos.itemIndex > so->currPos.lastItem)
+               /* Fetch the first page & tuple */
+               if (_bt_first(scan, ForwardScanDirection))
                {
-                       /* let _bt_next do the heavy lifting */
-                       if (!_bt_next(scan, ForwardScanDirection))
-                               break;
-               }
+                       /* Save tuple ID, and continue scanning */
+                       heapTid = &scan->xs_ctup.t_self;
+                       tbm_add_tuples(tbm, heapTid, 1, false);
+                       ntids++;
 
-               /* Save tuple ID, and continue scanning */
-               heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
-               tbm_add_tuples(tbm, heapTid, 1, false);
-               ntids++;
-       }
+                       for (;;)
+                       {
+                               /*
+                                * Advance to next tuple within page.  This is the same as the
+                                * easy case in _bt_next().
+                                */
+                               if (++so->currPos.itemIndex > so->currPos.lastItem)
+                               {
+                                       /* let _bt_next do the heavy lifting */
+                                       if (!_bt_next(scan, ForwardScanDirection))
+                                               break;
+                               }
+
+                               /* Save tuple ID, and continue scanning */
+                               heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
+                               tbm_add_tuples(tbm, heapTid, 1, false);
+                               ntids++;
+                       }
+               }
+               /* Now see if we have more array keys to deal with */
+       } while (so->numArrayKeys && _bt_advance_array_keys(scan, ForwardScanDirection));
 
        PG_RETURN_INT64(ntids);
 }
@@ -383,6 +422,12 @@ btbeginscan(PG_FUNCTION_ARGS)
                so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
        else
                so->keyData = NULL;
+
+       so->arrayKeyData = NULL;        /* assume no array keys for now */
+       so->numArrayKeys = 0;
+       so->arrayKeys = NULL;
+       so->arrayContext = NULL;
+
        so->killedItems = NULL;         /* until needed */
        so->numKilled = 0;
 
@@ -460,6 +505,9 @@ btrescan(PG_FUNCTION_ARGS)
                                scan->numberOfKeys * sizeof(ScanKeyData));
        so->numberOfKeys = 0;           /* until _bt_preprocess_keys sets it */
 
+       /* If any keys are SK_SEARCHARRAY type, set up array-key info */
+       _bt_preprocess_array_keys(scan);
+
        PG_RETURN_VOID();
 }
 
@@ -490,10 +538,13 @@ btendscan(PG_FUNCTION_ARGS)
        so->markItemIndex = -1;
 
        /* Release storage */
-       if (so->killedItems != NULL)
-               pfree(so->killedItems);
        if (so->keyData != NULL)
                pfree(so->keyData);
+       /* so->arrayKeyData and so->arrayKeys are in arrayContext */
+       if (so->arrayContext != NULL)
+               MemoryContextDelete(so->arrayContext);
+       if (so->killedItems != NULL)
+               pfree(so->killedItems);
        if (so->currTuples != NULL)
                pfree(so->currTuples);
        /* so->markTuples should not be pfree'd, see btrescan */
index b6fb3867bf0b908b50605abaa3627dedb6d19327..134abe6d596413a1ad54c0c7f378248251295d4a 100644 (file)
 #include "access/reloptions.h"
 #include "access/relscan.h"
 #include "miscadmin.h"
+#include "utils/array.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 
 
+typedef struct BTSortArrayContext
+{
+       FmgrInfo        flinfo;
+       Oid                     collation;
+       bool            reverse;
+} BTSortArrayContext;
+
+static Datum _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey,
+                                                StrategyNumber strat,
+                                                Datum *elems, int nelems);
+static int     _bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
+                                               bool reverse,
+                                               Datum *elems, int nelems);
+static int _bt_compare_array_elements(const void *a, const void *b, void *arg);
 static bool _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
                                                 ScanKey leftarg, ScanKey rightarg,
                                                 bool *result);
@@ -158,13 +174,433 @@ _bt_freestack(BTStack stack)
 }
 
 
+/*
+ *     _bt_preprocess_array_keys() -- Preprocess SK_SEARCHARRAY scan keys
+ *
+ * If there are any SK_SEARCHARRAY scan keys, deconstruct the array(s) and
+ * set up BTArrayKeyInfo info for each one that is an equality-type key.
+ * Prepare modified scan keys in so->arrayKeyData, which will hold the current
+ * array elements during each primitive indexscan operation.  For inequality
+ * array keys, it's sufficient to find the extreme element value and replace
+ * the whole array with that scalar value.
+ *
+ * Note: the reason we need so->arrayKeyData, rather than just scribbling
+ * on scan->keyData, is that callers are permitted to call btrescan without
+ * supplying a new set of scankey data.
+ */
+void
+_bt_preprocess_array_keys(IndexScanDesc scan)
+{
+       BTScanOpaque so = (BTScanOpaque) scan->opaque;
+       int                     numberOfKeys = scan->numberOfKeys;
+       int16      *indoption = scan->indexRelation->rd_indoption;
+       int                     numArrayKeys;
+       ScanKey         cur;
+       int                     i;
+       MemoryContext oldContext;
+
+       /* Quick check to see if there are any array keys */
+       numArrayKeys = 0;
+       for (i = 0; i < numberOfKeys; i++)
+       {
+               cur = &scan->keyData[i];
+               if (cur->sk_flags & SK_SEARCHARRAY)
+               {
+                       numArrayKeys++;
+                       Assert(!(cur->sk_flags & (SK_ROW_HEADER | SK_SEARCHNULL | SK_SEARCHNOTNULL)));
+                       /* If any arrays are null as a whole, we can quit right now. */
+                       if (cur->sk_flags & SK_ISNULL)
+                       {
+                               so->numArrayKeys = -1;
+                               so->arrayKeyData = NULL;
+                               return;
+                       }
+               }
+       }
+
+       /* Quit if nothing to do. */
+       if (numArrayKeys == 0)
+       {
+               so->numArrayKeys = 0;
+               so->arrayKeyData = NULL;
+               return;
+       }
+
+       /*
+        * Make a scan-lifespan context to hold array-associated data, or reset
+        * it if we already have one from a previous rescan cycle.
+        */
+       if (so->arrayContext == NULL)
+               so->arrayContext = AllocSetContextCreate(CurrentMemoryContext,
+                                                                                                "BTree Array Context",
+                                                                                                ALLOCSET_SMALL_MINSIZE,
+                                                                                                ALLOCSET_SMALL_INITSIZE,
+                                                                                                ALLOCSET_SMALL_MAXSIZE);
+       else
+               MemoryContextReset(so->arrayContext);
+
+       oldContext = MemoryContextSwitchTo(so->arrayContext);
+
+       /* Create modifiable copy of scan->keyData in the workspace context */
+       so->arrayKeyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
+       memcpy(so->arrayKeyData,
+                  scan->keyData,
+                  scan->numberOfKeys * sizeof(ScanKeyData));
+
+       /* Allocate space for per-array data in the workspace context */
+       so->arrayKeys = (BTArrayKeyInfo *) palloc0(numArrayKeys * sizeof(BTArrayKeyInfo));
+
+       /* Now process each array key */
+       numArrayKeys = 0;
+       for (i = 0; i < numberOfKeys; i++)
+       {
+               ArrayType  *arrayval;
+               int16           elmlen;
+               bool            elmbyval;
+               char            elmalign;
+               int                     num_elems;
+               Datum      *elem_values;
+               bool       *elem_nulls;
+               int                     num_nonnulls;
+               int                     j;
+
+               cur = &so->arrayKeyData[i];
+               if (!(cur->sk_flags & SK_SEARCHARRAY))
+                       continue;
+
+               /*
+                * First, deconstruct the array into elements.  Anything allocated
+                * here (including a possibly detoasted array value) is in the
+                * workspace context.
+                */
+               arrayval = DatumGetArrayTypeP(cur->sk_argument);
+               /* We could cache this data, but not clear it's worth it */
+               get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+                                                        &elmlen, &elmbyval, &elmalign);
+               deconstruct_array(arrayval,
+                                                 ARR_ELEMTYPE(arrayval),
+                                                 elmlen, elmbyval, elmalign,
+                                                 &elem_values, &elem_nulls, &num_elems);
+
+               /*
+                * Compress out any null elements.  We can ignore them since we assume
+                * all btree operators are strict.
+                */
+               num_nonnulls = 0;
+               for (j = 0; j < num_elems; j++)
+               {
+                       if (!elem_nulls[j])
+                               elem_values[num_nonnulls++] = elem_values[j];
+               }
+
+               /* We could pfree(elem_nulls) now, but not worth the cycles */
+
+               /* If there's no non-nulls, the scan qual is unsatisfiable */
+               if (num_nonnulls == 0)
+               {
+                       numArrayKeys = -1;
+                       break;
+               }
+
+               /*
+                * If the comparison operator is not equality, then the array qual
+                * degenerates to a simple comparison against the smallest or largest
+                * non-null array element, as appropriate.
+                */
+               switch (cur->sk_strategy)
+               {
+                       case BTLessStrategyNumber:
+                       case BTLessEqualStrategyNumber:
+                               cur->sk_argument =
+                                       _bt_find_extreme_element(scan, cur,
+                                                                                        BTGreaterStrategyNumber,
+                                                                                        elem_values, num_nonnulls);
+                               continue;
+                       case BTEqualStrategyNumber:
+                               /* proceed with rest of loop */
+                               break;
+                       case BTGreaterEqualStrategyNumber:
+                       case BTGreaterStrategyNumber:
+                               cur->sk_argument =
+                                       _bt_find_extreme_element(scan, cur,
+                                                                                        BTLessStrategyNumber,
+                                                                                        elem_values, num_nonnulls);
+                               continue;
+                       default:
+                               elog(ERROR, "unrecognized StrategyNumber: %d",
+                                        (int) cur->sk_strategy);
+                               break;
+               }
+
+               /*
+                * Sort the non-null elements and eliminate any duplicates.  We must
+                * sort in the same ordering used by the index column, so that the
+                * successive primitive indexscans produce data in index order.
+                */
+               num_elems = _bt_sort_array_elements(scan, cur,
+                                                                                       (indoption[cur->sk_attno - 1] & INDOPTION_DESC) != 0,
+                                                                                       elem_values, num_nonnulls);
+
+               /*
+                * And set up the BTArrayKeyInfo data.
+                */
+               so->arrayKeys[numArrayKeys].scan_key = i;
+               so->arrayKeys[numArrayKeys].num_elems = num_elems;
+               so->arrayKeys[numArrayKeys].elem_values = elem_values;
+               numArrayKeys++;
+       }
+
+       so->numArrayKeys = numArrayKeys;
+
+       MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * _bt_find_extreme_element() -- get least or greatest array element
+ *
+ * scan and skey identify the index column, whose opfamily determines the
+ * comparison semantics.  strat should be BTLessStrategyNumber to get the
+ * least element, or BTGreaterStrategyNumber to get the greatest.
+ */
+static Datum
+_bt_find_extreme_element(IndexScanDesc scan, ScanKey skey,
+                                                StrategyNumber strat,
+                                                Datum *elems, int nelems)
+{
+       Relation        rel = scan->indexRelation;
+       Oid                     elemtype,
+                               cmp_op;
+       RegProcedure cmp_proc;
+       FmgrInfo        flinfo;
+       Datum           result;
+       int                     i;
+
+       /*
+        * Determine the nominal datatype of the array elements.  We have to
+        * support the convention that sk_subtype == InvalidOid means the opclass
+        * input type; this is a hack to simplify life for ScanKeyInit().
+        */
+       elemtype = skey->sk_subtype;
+       if (elemtype == InvalidOid)
+               elemtype = rel->rd_opcintype[skey->sk_attno - 1];
+
+       /*
+        * Look up the appropriate comparison operator in the opfamily.
+        *
+        * Note: it's possible that this would fail, if the opfamily is incomplete,
+        * but it seems quite unlikely that an opfamily would omit non-cross-type
+        * comparison operators for any datatype that it supports at all.
+        */
+       cmp_op = get_opfamily_member(rel->rd_opfamily[skey->sk_attno - 1],
+                                                                elemtype,
+                                                                elemtype,
+                                                                strat);
+       if (!OidIsValid(cmp_op))
+               elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+                        strat, elemtype, elemtype,
+                        rel->rd_opfamily[skey->sk_attno - 1]);
+       cmp_proc = get_opcode(cmp_op);
+       if (!RegProcedureIsValid(cmp_proc))
+               elog(ERROR, "missing oprcode for operator %u", cmp_op);
+
+       fmgr_info(cmp_proc, &flinfo);
+
+       Assert(nelems > 0);
+       result = elems[0];
+       for (i = 1; i < nelems; i++)
+       {
+               if (DatumGetBool(FunctionCall2Coll(&flinfo,
+                                                                                  skey->sk_collation,
+                                                                                  elems[i],
+                                                                                  result)))
+                       result = elems[i];
+       }
+
+       return result;
+}
+
+/*
+ * _bt_sort_array_elements() -- sort and de-dup array elements
+ *
+ * The array elements are sorted in-place, and the new number of elements
+ * after duplicate removal is returned.
+ *
+ * scan and skey identify the index column, whose opfamily determines the
+ * comparison semantics.  If reverse is true, we sort in descending order.
+ */
+static int
+_bt_sort_array_elements(IndexScanDesc scan, ScanKey skey,
+                                               bool reverse,
+                                               Datum *elems, int nelems)
+{
+       Relation        rel = scan->indexRelation;
+       Oid                     elemtype;
+       RegProcedure cmp_proc;
+       BTSortArrayContext cxt;
+       int                     last_non_dup;
+       int                     i;
+
+       if (nelems <= 1)
+               return nelems;                  /* no work to do */
+
+       /*
+        * Determine the nominal datatype of the array elements.  We have to
+        * support the convention that sk_subtype == InvalidOid means the opclass
+        * input type; this is a hack to simplify life for ScanKeyInit().
+        */
+       elemtype = skey->sk_subtype;
+       if (elemtype == InvalidOid)
+               elemtype = rel->rd_opcintype[skey->sk_attno - 1];
+
+       /*
+        * Look up the appropriate comparison function in the opfamily.
+        *
+        * Note: it's possible that this would fail, if the opfamily is incomplete,
+        * but it seems quite unlikely that an opfamily would omit non-cross-type
+        * support functions for any datatype that it supports at all.
+        */
+       cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
+                                                                elemtype,
+                                                                elemtype,
+                                                                BTORDER_PROC);
+       if (!RegProcedureIsValid(cmp_proc))
+               elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
+                        BTORDER_PROC, elemtype, elemtype,
+                        rel->rd_opfamily[skey->sk_attno - 1]);
+
+       /* Sort the array elements */
+       fmgr_info(cmp_proc, &cxt.flinfo);
+       cxt.collation = skey->sk_collation;
+       cxt.reverse = reverse;
+       qsort_arg((void *) elems, nelems, sizeof(Datum),
+                         _bt_compare_array_elements, (void *) &cxt);
+
+       /* Now scan the sorted elements and remove duplicates */
+       last_non_dup = 0;
+       for (i = 1; i < nelems; i++)
+       {
+               int32           compare;
+
+               compare = DatumGetInt32(FunctionCall2Coll(&cxt.flinfo,
+                                                                                                 cxt.collation,
+                                                                                                 elems[last_non_dup],
+                                                                                                 elems[i]));
+               if (compare != 0)
+                       elems[++last_non_dup] = elems[i];
+       }
+
+       return last_non_dup + 1;
+}
+
+/*
+ * qsort_arg comparator for sorting array elements
+ */
+static int
+_bt_compare_array_elements(const void *a, const void *b, void *arg)
+{
+       Datum           da = *((const Datum *) a);
+       Datum           db = *((const Datum *) b);
+       BTSortArrayContext *cxt = (BTSortArrayContext *) arg;
+       int32           compare;
+
+       compare = DatumGetInt32(FunctionCall2Coll(&cxt->flinfo,
+                                                                                         cxt->collation,
+                                                                                         da, db));
+       if (cxt->reverse)
+               compare = -compare;
+       return compare;
+}
+
+/*
+ * _bt_start_array_keys() -- Initialize array keys at start of a scan
+ *
+ * Set up the cur_elem counters and fill in the first sk_argument value for
+ * each array scankey.  We can't do this until we know the scan direction.
+ */
+void
+_bt_start_array_keys(IndexScanDesc scan, ScanDirection dir)
+{
+       BTScanOpaque so = (BTScanOpaque) scan->opaque;
+       int                     i;
+
+       for (i = 0; i < so->numArrayKeys; i++)
+       {
+               BTArrayKeyInfo *curArrayKey = &so->arrayKeys[i];
+               ScanKey         skey = &so->arrayKeyData[curArrayKey->scan_key];
+
+               Assert(curArrayKey->num_elems > 0);
+               if (ScanDirectionIsBackward(dir))
+                       curArrayKey->cur_elem = curArrayKey->num_elems - 1;
+               else
+                       curArrayKey->cur_elem = 0;
+               skey->sk_argument = curArrayKey->elem_values[curArrayKey->cur_elem];
+       }
+}
+
+/*
+ * _bt_advance_array_keys() -- Advance to next set of array elements
+ *
+ * Returns TRUE if there is another set of values to consider, FALSE if not.
+ * On TRUE result, the scankeys are initialized with the next set of values.
+ */
+bool
+_bt_advance_array_keys(IndexScanDesc scan, ScanDirection dir)
+{
+       BTScanOpaque so = (BTScanOpaque) scan->opaque;
+       bool            found = false;
+       int                     i;
+
+       /*
+        * We must advance the last array key most quickly, since it will
+        * correspond to the lowest-order index column among the available
+        * qualifications. This is necessary to ensure correct ordering of output
+        * when there are multiple array keys.
+        */
+       for (i = so->numArrayKeys - 1; i >= 0; i--)
+       {
+               BTArrayKeyInfo *curArrayKey = &so->arrayKeys[i];
+               ScanKey         skey = &so->arrayKeyData[curArrayKey->scan_key];
+               int                     cur_elem = curArrayKey->cur_elem;
+               int                     num_elems = curArrayKey->num_elems;
+
+               if (ScanDirectionIsBackward(dir))
+               {
+                       if (--cur_elem < 0)
+                       {
+                               cur_elem = num_elems - 1;
+                               found = false;  /* need to advance next array key */
+                       }
+                       else
+                               found = true;
+               }
+               else
+               {
+                       if (++cur_elem >= num_elems)
+                       {
+                               cur_elem = 0;
+                               found = false;  /* need to advance next array key */
+                       }
+                       else
+                               found = true;
+               }
+
+               curArrayKey->cur_elem = cur_elem;
+               skey->sk_argument = curArrayKey->elem_values[cur_elem];
+               if (found)
+                       break;
+       }
+
+       return found;
+}
+
+
 /*
  *     _bt_preprocess_keys() -- Preprocess scan keys
  *
- * The caller-supplied search-type keys (in scan->keyData[]) are copied to
- * so->keyData[] with possible transformation. scan->numberOfKeys is
- * the number of input keys, so->numberOfKeys gets the number of output
- * keys (possibly less, never greater).
+ * The given search-type keys (in scan->keyData[] or so->arrayKeyData[])
+ * are copied to so->keyData[] with possible transformation.
+ * scan->numberOfKeys is the number of input keys, so->numberOfKeys gets
+ * the number of output keys (possibly less, never greater).
  *
  * The output keys are marked with additional sk_flag bits beyond the
  * system-standard bits supplied by the caller.  The DESC and NULLS_FIRST
@@ -226,8 +662,8 @@ _bt_freestack(BTStack stack)
  *
  * 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.
+ * key argument values, which could change on a rescan or after moving to
+ * new elements of array keys.  Therefore we can't overwrite the source data.
  */
 void
 _bt_preprocess_keys(IndexScanDesc scan)
@@ -253,7 +689,14 @@ _bt_preprocess_keys(IndexScanDesc scan)
        if (numberOfKeys < 1)
                return;                                 /* done if qual-less scan */
 
-       inkeys = scan->keyData;
+       /*
+        * Read so->arrayKeyData if array keys are present, else scan->keyData
+        */
+       if (so->arrayKeyData != NULL)
+               inkeys = so->arrayKeyData;
+       else
+               inkeys = scan->keyData;
+
        outkeys = so->keyData;
        cur = &inkeys[0];
        /* we check that input keys are correctly ordered */
index 6d073bf5fdb59ed4228b2d1bad58326414ad8f83..e3be5a2cae3c7d77c12ef6481de3d3fc3cf7905a 100644 (file)
@@ -647,11 +647,13 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * 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,
+ * 4. ScalarArrayOpExpr ("indexkey op ANY (array-expression)").  If the index
+ * has rd_am->amsearcharray, we handle these the same as simple operators,
+ * setting the SK_SEARCHARRAY flag to tell the AM to handle them.  Otherwise,
  * 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,
- * even if they happen to be constants.)
+ * (Note that if we use an IndexArrayKeyInfo struct, the array expression is
+ * always treated as requiring runtime evaluation, even if it's a constant.)
  *
  * 5. NullTest ("indexkey IS NULL/IS NOT NULL").  We just fill in the
  * ScanKey properly.
@@ -680,7 +682,7 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
  * *numArrayKeys: receives number of array keys
  *
  * Caller may pass NULL for arrayKeys and numArrayKeys to indicate that
- * ScalarArrayOpExpr quals are not supported.
+ * IndexArrayKeyInfos are not supported.
  */
 void
 ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
@@ -981,6 +983,8 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
                {
                        /* indexkey op ANY (array-expression) */
                        ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+                       int                     flags = 0;
+                       Datum           scanvalue;
 
                        Assert(!isorderby);
 
@@ -1027,23 +1031,72 @@ ExecIndexBuildScanKeys(PlanState *planstate, Relation index,
 
                        Assert(rightop != NULL);
 
-                       array_keys[n_array_keys].scan_key = this_scan_key;
-                       array_keys[n_array_keys].array_expr =
-                               ExecInitExpr(rightop, planstate);
-                       /* the remaining fields were zeroed by palloc0 */
-                       n_array_keys++;
+                       if (index->rd_am->amsearcharray)
+                       {
+                               /* Index AM will handle this like a simple operator */
+                               flags |= SK_SEARCHARRAY;
+                               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 */
+                                       if (n_runtime_keys >= max_runtime_keys)
+                                       {
+                                               if (max_runtime_keys == 0)
+                                               {
+                                                       max_runtime_keys = 8;
+                                                       runtime_keys = (IndexRuntimeKeyInfo *)
+                                                               palloc(max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
+                                               }
+                                               else
+                                               {
+                                                       max_runtime_keys *= 2;
+                                                       runtime_keys = (IndexRuntimeKeyInfo *)
+                                                               repalloc(runtime_keys, max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
+                                               }
+                                       }
+                                       runtime_keys[n_runtime_keys].scan_key = this_scan_key;
+                                       runtime_keys[n_runtime_keys].key_expr =
+                                               ExecInitExpr(rightop, planstate);
+
+                                       /*
+                                        * Careful here: the runtime expression is not of
+                                        * op_righttype, but rather is an array of same; so
+                                        * TypeIsToastable() isn't helpful.  However, we can
+                                        * assume that all array types are toastable.
+                                        */
+                                       runtime_keys[n_runtime_keys].key_toastable = true;
+                                       n_runtime_keys++;
+                                       scanvalue = (Datum) 0;
+                               }
+                       }
+                       else
+                       {
+                               /* Executor has to expand the array value */
+                               array_keys[n_array_keys].scan_key = this_scan_key;
+                               array_keys[n_array_keys].array_expr =
+                                       ExecInitExpr(rightop, planstate);
+                               /* the remaining fields were zeroed by palloc0 */
+                               n_array_keys++;
+                               scanvalue = (Datum) 0;
+                       }
 
                        /*
                         * initialize the scan key's fields appropriately
                         */
                        ScanKeyEntryInitialize(this_scan_key,
-                                                                  0,   /* flags */
+                                                                  flags,
                                                                   varattno,    /* attribute number to scan */
                                                                   op_strategy, /* op's strategy */
                                                                   op_righttype,                /* strategy subtype */
                                                                   saop->inputcollid,   /* collation */
                                                                   opfuncid,    /* reg proc to use */
-                                                                  (Datum) 0);  /* constant */
+                                                                  scanvalue);  /* constant */
                }
                else if (IsA(clause, NullTest))
                {
index f821b508d6c888870b30eb8891a02aea1241b63b..348c36b40e3cc0912829d94d8d3e851bbf560d5e 100644 (file)
@@ -394,9 +394,14 @@ cost_index(IndexPath *path, PlannerInfo *root,
                if (indexonly)
                        pages_fetched = ceil(pages_fetched * (1.0 - baserel->allvisfrac));
 
-               min_IO_cost = spc_random_page_cost;
-               if (pages_fetched > 1)
-                       min_IO_cost += (pages_fetched - 1) * spc_seq_page_cost;
+               if (pages_fetched > 0)
+               {
+                       min_IO_cost = spc_random_page_cost;
+                       if (pages_fetched > 1)
+                               min_IO_cost += (pages_fetched - 1) * spc_seq_page_cost;
+               }
+               else
+                       min_IO_cost = 0;
        }
 
        /*
index ece326d88507bb202ce9d049d1023ef531f7a829..940efb38b668dac8a8e773f996efcda5267ca6c1 100644 (file)
@@ -48,9 +48,9 @@
 /* Whether to use ScalarArrayOpExpr to build index qualifications */
 typedef enum
 {
-       SAOP_FORBID,                            /* Do not use ScalarArrayOpExpr */
-       SAOP_ALLOW,                                     /* OK to use ScalarArrayOpExpr */
-       SAOP_REQUIRE                            /* Require ScalarArrayOpExpr */
+       SAOP_PER_AM,                            /* Use ScalarArrayOpExpr if amsearcharray */
+       SAOP_ALLOW,                                     /* Use ScalarArrayOpExpr for all indexes */
+       SAOP_REQUIRE                            /* Require ScalarArrayOpExpr to be used */
 } SaOpControl;
 
 /* Whether we are looking for plain indexscan, bitmap scan, or either */
@@ -196,7 +196,7 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
         */
        indexpaths = find_usable_indexes(root, rel,
                                                                         rel->baserestrictinfo, NIL,
-                                                                        true, NULL, SAOP_FORBID, ST_ANYSCAN);
+                                                                        true, NULL, SAOP_PER_AM, ST_ANYSCAN);
 
        /*
         * Submit all the ones that can form plain IndexScan plans to add_path.
@@ -233,8 +233,9 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
        bitindexpaths = list_concat(bitindexpaths, indexpaths);
 
        /*
-        * Likewise, generate paths using ScalarArrayOpExpr clauses; these can't
-        * be simple indexscans but they can be used in bitmap scans.
+        * Likewise, generate paths using executor-managed ScalarArrayOpExpr
+        * clauses; these can't be simple indexscans but they can be used in
+        * bitmap scans.
         */
        indexpaths = find_saop_paths(root, rel,
                                                                 rel->baserestrictinfo, NIL,
@@ -337,6 +338,14 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
                                break;
                }
 
+               /*
+                * If we're doing find_saop_paths(), we can skip indexes that support
+                * ScalarArrayOpExpr natively.  We already generated all the potential
+                * indexpaths for them, so no need to do anything more.
+                */
+               if (saop_control == SAOP_REQUIRE && index->amsearcharray)
+                       continue;
+
                /*
                 * Ignore partial indexes that do not match the query.  If a partial
                 * index is marked predOK then we know it's OK; otherwise, if we are
@@ -492,10 +501,10 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
 
 /*
  * find_saop_paths
- *             Find all the potential indexpaths that make use of ScalarArrayOpExpr
- *             clauses.  The executor only supports these in bitmap scans, not
- *             plain indexscans, so we need to segregate them from the normal case.
- *             Otherwise, same API as find_usable_indexes().
+ *             Find all the potential indexpaths that make use of executor-managed
+ *             ScalarArrayOpExpr clauses.  The executor only supports these in bitmap
+ *             scans, not plain indexscans, so we need to segregate them from the
+ *             normal case.  Otherwise, same API as find_usable_indexes().
  *             Returns a list of IndexPaths.
  */
 static List *
@@ -1287,9 +1296,10 @@ group_clauses_by_indexkey(IndexOptInfo *index,
  *       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,
- *       our caller specifies whether to allow these or not.
+ *       the clause is of the form "indexkey op ANY (arrayconst)".  Since not
+ *       all indexes handle these natively, and the executor implements them
+ *       only in the context of bitmap index scans, our caller specifies whether
+ *       to allow these or not.
  *
  *       For boolean indexes, it is also possible to match the clause directly
  *       to the indexkey; or perhaps the clause is (NOT indexkey).
@@ -1357,8 +1367,8 @@ match_clause_to_indexcol(IndexOptInfo *index,
                expr_coll = ((OpExpr *) clause)->inputcollid;
                plain_op = true;
        }
-       else if (saop_control != SAOP_FORBID &&
-                        clause && IsA(clause, ScalarArrayOpExpr))
+       else if (clause && IsA(clause, ScalarArrayOpExpr) &&
+                        (index->amsearcharray || saop_control != SAOP_PER_AM))
        {
                ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
 
@@ -2089,12 +2099,12 @@ best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel,
 
        /*
         * Find all the index paths that are usable for this join, except for
-        * stuff involving OR and ScalarArrayOpExpr clauses.
+        * stuff involving OR and executor-managed ScalarArrayOpExpr clauses.
         */
        allindexpaths = find_usable_indexes(root, rel,
                                                                                clause_list, NIL,
                                                                                false, outer_rel,
-                                                                               SAOP_FORBID,
+                                                                               SAOP_PER_AM,
                                                                                ST_ANYSCAN);
 
        /*
@@ -2123,8 +2133,9 @@ best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel,
                                                                                                                 outer_rel));
 
        /*
-        * Likewise, generate paths using ScalarArrayOpExpr clauses; these can't
-        * be simple indexscans but they can be used in bitmap scans.
+        * Likewise, generate paths using executor-managed ScalarArrayOpExpr
+        * clauses; these can't be simple indexscans but they can be used in
+        * bitmap scans.
         */
        bitindexpaths = list_concat(bitindexpaths,
                                                                find_saop_paths(root, rel,
index aa436004f89894c3c8e723a1a1d71168f9dec625..bb8095224247694b0630ea6ea7947e980dbc45f5 100644 (file)
@@ -215,6 +215,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
                        info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop;
                        info->amcanreturn = indexRelation->rd_am->amcanreturn;
                        info->amoptionalkey = indexRelation->rd_am->amoptionalkey;
+                       info->amsearcharray = indexRelation->rd_am->amsearcharray;
                        info->amsearchnulls = indexRelation->rd_am->amsearchnulls;
                        info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple);
                        info->amhasgetbitmap = OidIsValid(indexRelation->rd_am->amgetbitmap);
index 8ceea820bdc4eb2e6c737c2f98eb1f0bf6fa034b..96946281dabe1d0f2db82878d536043733b4a4ff 100644 (file)
@@ -6385,14 +6385,7 @@ btcostestimate(PG_FUNCTION_ARGS)
         * is that multiple columns dilute the importance of the first column's
         * ordering, but don't negate it entirely.  Before 8.0 we divided the
         * correlation by the number of columns, but that seems too strong.)
-        *
-        * We can skip all this if we found a ScalarArrayOpExpr, because then the
-        * call must be for a bitmap index scan, and the caller isn't going to
-        * care what the index correlation is.
         */
-       if (found_saop)
-               PG_RETURN_VOID();
-
        MemSet(&vardata, 0, sizeof(vardata));
 
        if (index->indexkeys[0] != 0)
index 199fc940267933fd66b2994f594286b90fa69c2c..347d9423ba3d092235033d2447fe12f21f43011d 100644 (file)
@@ -525,6 +525,15 @@ typedef BTScanPosData *BTScanPos;
 
 #define BTScanPosIsValid(scanpos) BufferIsValid((scanpos).buf)
 
+/* We need one of these for each equality-type SK_SEARCHARRAY scan key */
+typedef struct BTArrayKeyInfo
+{
+       int                     scan_key;               /* index of associated key in arrayKeyData */
+       int                     cur_elem;               /* index of current element in elem_values */
+       int                     num_elems;              /* number of elems in current array value */
+       Datum      *elem_values;        /* array of num_elems Datums */
+} BTArrayKeyInfo;
+
 typedef struct BTScanOpaqueData
 {
        /* these fields are set by _bt_preprocess_keys(): */
@@ -532,6 +541,13 @@ typedef struct BTScanOpaqueData
        int                     numberOfKeys;   /* number of preprocessed scan keys */
        ScanKey         keyData;                /* array of preprocessed scan keys */
 
+       /* workspace for SK_SEARCHARRAY support */
+       ScanKey         arrayKeyData;   /* modified copy of scan->keyData */
+       int                     numArrayKeys;   /* number of equality-type array keys (-1 if
+                                                                * there are any unsatisfiable array keys) */
+       BTArrayKeyInfo *arrayKeys;      /* info about each equality-type array key */
+       MemoryContext arrayContext;     /* scan-lifespan context for array data */
+
        /* info about killed items if any (killedItems is NULL if never used) */
        int                *killedItems;        /* currPos.items indexes of killed items */
        int                     numKilled;              /* number of currently stored items */
@@ -639,6 +655,9 @@ extern ScanKey _bt_mkscankey(Relation rel, IndexTuple itup);
 extern ScanKey _bt_mkscankey_nodata(Relation rel);
 extern void _bt_freeskey(ScanKey skey);
 extern void _bt_freestack(BTStack stack);
+extern void _bt_preprocess_array_keys(IndexScanDesc scan);
+extern void _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir);
+extern bool _bt_advance_array_keys(IndexScanDesc scan, ScanDirection dir);
 extern void _bt_preprocess_keys(IndexScanDesc scan);
 extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
                          Page page, OffsetNumber offnum,
index a82e46ee0e1b8c5ed8ebaff11cc031ff6764358c..b9c61cd10ae05cfa666406b96691881220735159 100644 (file)
@@ -55,18 +55,27 @@ typedef uint16 StrategyNumber;
  * If the operator is collation-sensitive, sk_collation must be set
  * correctly as well.
  *
+ * A ScanKey can also represent a ScalarArrayOpExpr, that is a condition
+ * "column op ANY(ARRAY[...])".  This is signaled by the SK_SEARCHARRAY
+ * flag bit.  The sk_argument is not a value of the operator's right-hand
+ * argument type, but rather an array of such values, and the per-element
+ * comparisons are to be ORed together.
+ *
  * A ScanKey can also represent a condition "column IS NULL" or "column
  * IS NOT NULL"; these cases are signaled by the SK_SEARCHNULL and
  * SK_SEARCHNOTNULL flag bits respectively.  The argument is always NULL,
  * and the sk_strategy, sk_subtype, sk_collation, and sk_func fields are
- * not used (unless set by the index AM).  Currently, SK_SEARCHNULL and
- * SK_SEARCHNOTNULL are supported only for index scans, not heap scans;
- * and not all index AMs support them.
+ * not used (unless set by the index AM).
+ *
+ * SK_SEARCHARRAY, SK_SEARCHNULL and SK_SEARCHNOTNULL are supported only
+ * for index scans, not heap scans; and not all index AMs support them,
+ * only those that set amsearcharray or amsearchnulls respectively.
  *
  * A ScanKey can also represent an ordering operator invocation, that is
  * an ordering requirement "ORDER BY indexedcol op constant".  This looks
  * the same as a comparison operator, except that the operator doesn't
  * (usually) yield boolean.  We mark such ScanKeys with SK_ORDER_BY.
+ * SK_SEARCHARRAY, SK_SEARCHNULL, SK_SEARCHNOTNULL cannot be used here.
  *
  * Note: in some places, ScanKeys are used as a convenient representation
  * for the invocation of an access method support procedure.  In this case
@@ -114,6 +123,7 @@ typedef ScanKeyData *ScanKey;
  *                             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.
+ * SK_SEARCHARRAY, SK_SEARCHNULL, SK_SEARCHNOTNULL cannot be used here.
  */
 
 /*
@@ -128,10 +138,11 @@ typedef ScanKeyData *ScanKey;
 #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 */
-#define SK_SEARCHNULL          0x0020          /* scankey represents "col IS NULL" */
-#define SK_SEARCHNOTNULL       0x0040          /* scankey represents "col IS NOT
+#define SK_SEARCHARRAY         0x0020          /* scankey represents ScalarArrayOp */
+#define SK_SEARCHNULL          0x0040          /* scankey represents "col IS NULL" */
+#define SK_SEARCHNOTNULL       0x0080          /* scankey represents "col IS NOT
                                                                                 * NULL" */
-#define SK_ORDER_BY                    0x0080          /* scankey is for ORDER BY op */
+#define SK_ORDER_BY                    0x0100          /* scankey is for ORDER BY op */
 
 
 /*
index 8fff3675ef2b601e5ded0e41a0a3da587bc2bdde..8097545faaaafb95f0832a659ed846b938451d4a 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201110141
+#define CATALOG_VERSION_NO     201110161
 
 #endif
index c3c864f95f55568ac03c3eb1bb2af15d5df278fa..8b075d30d689a9f7fa022d3f554626b6f5086883 100644 (file)
@@ -47,6 +47,7 @@ CATALOG(pg_am,2601)
        bool            amcanmulticol;  /* does AM support multi-column indexes? */
        bool            amcanreturn;    /* can AM return IndexTuples? */
        bool            amoptionalkey;  /* can query omit key for the first column? */
+       bool            amsearcharray;  /* can AM handle ScalarArrayOpExpr quals? */
        bool            amsearchnulls;  /* can AM search for NULL/NOT NULL entries? */
        bool            amstorage;              /* can storage type differ from column type? */
        bool            amclusterable;  /* does AM support cluster command? */
@@ -79,7 +80,7 @@ typedef FormData_pg_am *Form_pg_am;
  *             compiler constants for pg_am
  * ----------------
  */
-#define Natts_pg_am                                            29
+#define Natts_pg_am                                            30
 #define Anum_pg_am_amname                              1
 #define Anum_pg_am_amstrategies                        2
 #define Anum_pg_am_amsupport                   3
@@ -90,41 +91,42 @@ typedef FormData_pg_am *Form_pg_am;
 #define Anum_pg_am_amcanmulticol               8
 #define Anum_pg_am_amcanreturn                 9
 #define Anum_pg_am_amoptionalkey               10
-#define Anum_pg_am_amsearchnulls               11
-#define Anum_pg_am_amstorage                   12
-#define Anum_pg_am_amclusterable               13
-#define Anum_pg_am_ampredlocks                 14
-#define Anum_pg_am_amkeytype                   15
-#define Anum_pg_am_aminsert                            16
-#define Anum_pg_am_ambeginscan                 17
-#define Anum_pg_am_amgettuple                  18
-#define Anum_pg_am_amgetbitmap                 19
-#define Anum_pg_am_amrescan                            20
-#define Anum_pg_am_amendscan                   21
-#define Anum_pg_am_ammarkpos                   22
-#define Anum_pg_am_amrestrpos                  23
-#define Anum_pg_am_ambuild                             24
-#define Anum_pg_am_ambuildempty                        25
-#define Anum_pg_am_ambulkdelete                        26
-#define Anum_pg_am_amvacuumcleanup             27
-#define Anum_pg_am_amcostestimate              28
-#define Anum_pg_am_amoptions                   29
+#define Anum_pg_am_amsearcharray               11
+#define Anum_pg_am_amsearchnulls               12
+#define Anum_pg_am_amstorage                   13
+#define Anum_pg_am_amclusterable               14
+#define Anum_pg_am_ampredlocks                 15
+#define Anum_pg_am_amkeytype                   16
+#define Anum_pg_am_aminsert                            17
+#define Anum_pg_am_ambeginscan                 18
+#define Anum_pg_am_amgettuple                  19
+#define Anum_pg_am_amgetbitmap                 20
+#define Anum_pg_am_amrescan                            21
+#define Anum_pg_am_amendscan                   22
+#define Anum_pg_am_ammarkpos                   23
+#define Anum_pg_am_amrestrpos                  24
+#define Anum_pg_am_ambuild                             25
+#define Anum_pg_am_ambuildempty                        26
+#define Anum_pg_am_ambulkdelete                        27
+#define Anum_pg_am_amvacuumcleanup             28
+#define Anum_pg_am_amcostestimate              29
+#define Anum_pg_am_amoptions                   30
 
 /* ----------------
  *             initial contents of pg_am
  * ----------------
  */
 
-DATA(insert OID = 403 (  btree 5 1 t f t t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
+DATA(insert OID = 403 (  btree 5 1 t f t t t t t t f t t 0 btinsert btbeginscan btgettuple btgetbitmap btrescan btendscan btmarkpos btrestrpos btbuild btbuildempty btbulkdelete btvacuumcleanup btcostestimate btoptions ));
 DESCR("b-tree index access method");
 #define BTREE_AM_OID 403
-DATA(insert OID = 405 (  hash  1 1 f f t f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
+DATA(insert OID = 405 (  hash  1 1 f f t f f f f f f f f 23 hashinsert hashbeginscan hashgettuple hashgetbitmap hashrescan hashendscan hashmarkpos hashrestrpos hashbuild hashbuildempty hashbulkdelete hashvacuumcleanup hashcostestimate hashoptions ));
 DESCR("hash index access method");
 #define HASH_AM_OID 405
-DATA(insert OID = 783 (  gist  0 8 f t f f t f t t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
+DATA(insert OID = 783 (  gist  0 8 f t f f t f t t t t f 0 gistinsert gistbeginscan gistgettuple gistgetbitmap gistrescan gistendscan gistmarkpos gistrestrpos gistbuild gistbuildempty gistbulkdelete gistvacuumcleanup gistcostestimate gistoptions ));
 DESCR("GiST index access method");
 #define GIST_AM_OID 783
-DATA(insert OID = 2742 (  gin  0 5 f f f f t f t f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
+DATA(insert OID = 2742 (  gin  0 5 f f f f t f t f t f f 0 gininsert ginbeginscan - gingetbitmap ginrescan ginendscan ginmarkpos ginrestrpos ginbuild ginbuildempty ginbulkdelete ginvacuumcleanup gincostestimate ginoptions ));
 DESCR("GIN index access method");
 #define GIN_AM_OID 2742
 
index ef84e9f138de6988b277d216ede509465e53f865..2925d7e76598d50a9bb0cdf9a4ede12338a849c4 100644 (file)
@@ -490,8 +490,9 @@ typedef struct IndexOptInfo
        bool            unique;                 /* true if a unique index */
        bool            hypothetical;   /* true if index doesn't really exist */
        bool            amcanorderbyop; /* does AM support order by operator result? */
-       bool            amcanreturn;    /* does AM know how to return tuples? */
+       bool            amcanreturn;    /* can AM return IndexTuples? */
        bool            amoptionalkey;  /* can query omit key for the first column? */
+       bool            amsearcharray;  /* can AM handle ScalarArrayOpExpr quals? */
        bool            amsearchnulls;  /* can AM search for NULL/NOT NULL entries? */
        bool            amhasgettuple;  /* does AM have amgettuple interface? */
        bool            amhasgetbitmap; /* does AM have amgetbitmap interface? */