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>
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);
}
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);
}
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;
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();
}
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 */
#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);
}
+/*
+ * _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
*
* 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)
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 */
* 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.
* *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,
{
/* indexkey op ANY (array-expression) */
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) clause;
+ int flags = 0;
+ Datum scanvalue;
Assert(!isorderby);
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))
{
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;
}
/*
/* 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 */
*/
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.
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,
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
/*
* 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 *
* 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).
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;
/*
* 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);
/*
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,
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);
* 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)
#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(): */
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 */
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,
* 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
* 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.
*/
/*
#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 */
/*
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201110141
+#define CATALOG_VERSION_NO 201110161
#endif
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? */
* 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
#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 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 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 f 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 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
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? */