if (numberOfKeys < 1) return; /* done if qual-less scan */
/* If any keys are SK_SEARCHARRAY type, set up array-key info */
arrayKeyData = _bt_preprocess_array_keys(scan, &numberOfKeys); if (!so->qual_ok)
{ /* unmatchable array, so give up */ return;
}
/* we check that input keys are correctly ordered */ if (inkeys[0].sk_attno < 1)
elog(ERROR, "btree index keys must be ordered by attribute");
/* We can short-circuit most of the work if there's just one key */ if (numberOfKeys == 1)
{ /* Apply indoption to scankey (might change sk_strategy!) */ if (!_bt_fix_scankey_strategy(&inkeys[0], indoption))
so->qual_ok = false;
memcpy(&so->keyData[0], &inkeys[0], sizeof(ScanKeyData));
so->numberOfKeys = 1; /* We can mark the qual as required if it's for first index col */ if (inkeys[0].sk_attno == 1)
_bt_mark_scankey_required(&so->keyData[0]); if (arrayKeyData)
{ /* *Don'tcall_bt_preprocess_array_keys_finalinthisfastpath *(we'llmissoutonthesinglevaluearraytransformation,but *that'snotnearlyasimportantwhenthere'sonlyonescankey)
*/
Assert(so->keyData[0].sk_flags & SK_SEARCHARRAY);
Assert(so->keyData[0].sk_strategy != BTEqualStrategyNumber ||
(so->arrayKeys[0].scan_key == 0 &&
!(so->keyData[0].sk_flags & SK_BT_SKIP) &&
OidIsValid(so->orderProcs[0].fn_oid)));
}
/* *Loopiteratesfrom0tonumberOfKeysinclusive;weusethelastpassto *handleafter-last-keyprocessing.Actualexitfromtheloopisatthe *"break"statementbelow.
*/ for (int i = 0;; i++)
{
ScanKey inkey = inkeys + i; int j;
if (i < numberOfKeys)
{ /* Apply indoption to scankey (might change sk_strategy!) */ if (!_bt_fix_scankey_strategy(inkey, indoption))
{ /* NULL can't be matched, so give up */
so->qual_ok = false; return;
}
}
/* *Ifweareattheendofthekeysforaparticularattr,finishup *processingandemitthecleaned-upkeys.
*/ if (i == numberOfKeys || inkey->sk_attno != attno)
{ int priorNumberOfEqualCols = numberOfEqualCols;
/* check input keys are correctly ordered */ if (i < numberOfKeys && inkey->sk_attno < attno)
elog(ERROR, "btree index keys must be ordered by attribute");
/* *Wetreatallbtreeoperatorsasstrict(evenifthey'renotsomarked *inpg_proc).Thismeansthatitisimpossibleforanoperatorcondition *withaNULLcomparisonconstanttosucceed,andwecanrejectitright *away. * *However,wenowalsosupport"xISNULL"clausesassearchconditions, *sointhatcasekeepgoing.Theplannerhasnotfilledinany *particularstrategyinthiscase,sosetittoBTEqualStrategyNumber *---wecantreatISNULLasanequalityoperatorforpurposesofsearch *strategy. * *Likewise,"xISNOTNULL"issupported.Wetreatthataseither"less *thanNULL"inaNULLSLASTindex,or"greaterthanNULL"inaNULLS *FIRSTindex. * *Note:somedaywemighthavetofillinsk_collationfromtheindex *column'scollation.Atthemomentthisisanon-issuebecausewe'll *neveractuallycallthecomparisonoperatoronaNULL.
*/ if (skey->sk_flags & SK_ISNULL)
{ /* SK_ISNULL shouldn't be set in a row header scankey */
Assert(!(skey->sk_flags & SK_ROW_HEADER));
/* Set indoption flags in scankey (might be done already) */
skey->sk_flags |= addflags;
/* Set correct strategy for IS NULL or NOT NULL search */ if (skey->sk_flags & SK_SEARCHNULL)
{
skey->sk_strategy = BTEqualStrategyNumber;
skey->sk_subtype = InvalidOid;
skey->sk_collation = InvalidOid;
} elseif (skey->sk_flags & SK_SEARCHNOTNULL)
{ if (skey->sk_flags & SK_BT_NULLS_FIRST)
skey->sk_strategy = BTGreaterStrategyNumber; else
skey->sk_strategy = BTLessStrategyNumber;
skey->sk_subtype = InvalidOid;
skey->sk_collation = InvalidOid;
} else
{ /* regular qual, so it cannot be satisfied */ returnfalse;
}
/* Needn't do the rest */ returntrue;
}
/* Adjust strategy for DESC, if we didn't already */ if ((addflags & SK_BT_DESC) && !(skey->sk_flags & SK_BT_DESC))
skey->sk_strategy = BTCommuteStrategyNumber(skey->sk_strategy);
skey->sk_flags |= addflags;
/* If it's a row header, fix row member flags and strategies similarly */ if (skey->sk_flags & SK_ROW_HEADER)
{
ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument);
if (subkey->sk_flags & SK_ISNULL)
{ /* First row member is NULL, so RowCompare is unsatisfiable */
Assert(subkey->sk_flags & SK_ROW_MEMBER); returnfalse;
}
switch (skey->sk_strategy)
{ case BTLessStrategyNumber:
cmpexact = 1; /* exclude exact match, if any */ /* FALL THRU */ case BTLessEqualStrategyNumber: if (cmpresult >= cmpexact)
matchelem++; /* Resize, keeping elements from the start of the array */
new_nelems = matchelem; break; case BTEqualStrategyNumber: if (cmpresult != 0)
{ /* qual is unsatisfiable */
new_nelems = 0;
} else
{ /* Shift matching element to the start of the array, resize */
array->elem_values[0] = array->elem_values[matchelem];
new_nelems = 1;
} break; case BTGreaterEqualStrategyNumber:
cmpexact = 1; /* include exact match, if any */ /* FALL THRU */ case BTGreaterStrategyNumber: if (cmpresult >= cmpexact)
matchelem++; /* Shift matching elements to the start of the array, resize */
new_nelems = array->num_elems - matchelem;
memmove(array->elem_values, array->elem_values + matchelem, sizeof(Datum) * new_nelems); break; default:
elog(ERROR, "unrecognized StrategyNumber: %d",
(int) skey->sk_strategy); break;
}
/* Decrement, handling underflow by marking the qual unsatisfiable */
new_sk_argument = array->sksup->decrement(rel, orig_sk_argument, &uflow); if (uflow)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
so->qual_ok = false; return;
}
/* *Lookup<=operator(mightfail),accountingforthefactthata *high_compareonaDESCcolumnalreadyhaditsstrategycommuted
*/
lookupstrat = BTLessEqualStrategyNumber; if (high_compare->sk_flags & SK_BT_DESC)
lookupstrat = BTGreaterEqualStrategyNumber; /* commute this too */
leop = get_opfamily_member(opfamily, opcintype, opcintype, lookupstrat); if (!OidIsValid(leop)) return;
cmp_proc = get_opcode(leop); if (RegProcedureIsValid(cmp_proc))
{ /* Transform < high_compare key into <= key */
fmgr_info(cmp_proc, &high_compare->sk_func);
high_compare->sk_argument = new_sk_argument;
high_compare->sk_strategy = BTLessEqualStrategyNumber;
}
}
/* Increment, handling overflow by marking the qual unsatisfiable */
new_sk_argument = array->sksup->increment(rel, orig_sk_argument, &oflow); if (oflow)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
so->qual_ok = false; return;
}
/* *Lookup>=operator(mightfail),accountingforthefactthata *low_compareonaDESCcolumnalreadyhaditsstrategycommuted
*/
lookupstrat = BTGreaterEqualStrategyNumber; if (low_compare->sk_flags & SK_BT_DESC)
lookupstrat = BTLessEqualStrategyNumber; /* commute this too */
geop = get_opfamily_member(opfamily, opcintype, opcintype, lookupstrat); if (!OidIsValid(geop)) return;
cmp_proc = get_opcode(geop); if (RegProcedureIsValid(cmp_proc))
{ /* Transform > low_compare key into >= key */
fmgr_info(cmp_proc, &low_compare->sk_func);
low_compare->sk_argument = new_sk_argument;
low_compare->sk_strategy = BTGreaterEqualStrategyNumber;
}
}
/* Set things up for first key's attribute */
attno = so->keyData[0].sk_attno;
firsti = 0;
haveReqEquals = false;
haveReqForward = false;
haveReqBackward = false; for (int i = 0; i < so->numberOfKeys; i++)
{
ScanKey origkey = &so->keyData[i];
if (origkey->sk_attno != attno)
{ /* Reset for next attribute */
attno = origkey->sk_attno;
firsti = i;
/* Also fix-up array->scan_key references */ for (int arridx = 0; arridx < so->numArrayKeys; arridx++)
{
BTArrayKeyInfo *array = &so->arrayKeys[arridx];
array->scan_key = keyDataMap[array->scan_key];
}
/*
* Sort so->arrayKeys[] based on its new BTArrayKeyInfo.scan_key
* offsets, so that its order matches so->keyData[] order as expected
*/
qsort(so->arrayKeys, so->numArrayKeys, sizeof(BTArrayKeyInfo),
_bt_reorder_array_cmp);
/* Done with temp arrays */
pfree(unmarkOrderProcs);
pfree(keepOrderProcs);
}
}
/*
* _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.
* Returns modified scan keys as input for further, standard preprocessing.
*
* Currently we perform two kinds of preprocessing to deal with redundancies.
* For inequality array keys, it's sufficient to find the extreme element
* value and replace the whole array with that scalar value. This eliminates
* all but one array element as redundant. Similarly, we are capable of
* "merging together" multiple equality array keys (from two or more input
* scan keys) into a single output scan key containing only the intersecting
* array elements. This can eliminate many redundant array elements, as well
* as eliminating whole array scan keys as redundant. It can also allow us to
* detect contradictory quals.
*
* Caller must pass *new_numberOfKeys to give us a way to change the number of
* scan keys that caller treats as input to standard preprocessing steps. The
* returned array is smaller than scan->keyData[] when we could eliminate a
* redundant array scan key (redundant with another array scan key). It is
* convenient for _bt_preprocess_keys caller to have to deal with no more than
* one equality strategy array scan key per index attribute. We'll always be
* able to set things up that way when complete opfamilies are used.
*
* We're also responsible for generating skip arrays (and their associated
* scan keys) here. This enables skip scan. We do this for index attributes
* that initially lacked an equality condition within scan->keyData[], iff
* doing so allows a later scan key (that was passed to us in scan->keyData[])
* to be marked required by our _bt_preprocess_keys caller.
*
* We set the scan key references from the scan's BTArrayKeyInfo info array to
* offsets into the temp modified input array returned to caller. Scans that
* have array keys should call _bt_preprocess_array_keys_final when standard
* preprocessing steps are complete. This will convert the scan key offset
* references into references to the scan's so->keyData[] output scan keys.
*
* Note: the reason we need to return a temp scan key array, rather than just
* modifying scan->keyData[], is that callers are permitted to call btrescan
* without supplying a new set of scankey data. Certain other preprocessing
* routines (e.g., _bt_fix_scankey_strategy) _can_ modify scan->keyData[], but
* we can't make that work here because our modifications are non-idempotent.
*/
static ScanKey
_bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
Relation rel = scan->indexRelation;
int16 *indoption = rel->rd_indoption;
Oid skip_eq_ops[INDEX_MAX_KEYS];
int numArrayKeys,
numSkipArrayKeys,
numArrayKeyData;
AttrNumber attno_skip = 1;
int origarrayatt = InvalidAttrNumber,
origarraykey = -1;
Oid origelemtype = InvalidOid;
MemoryContext oldContext;
ScanKey arrayKeyData; /* modified copy of scan->keyData */
/*
* Check the number of input array keys within scan->keyData[] input keys
* (also checks if we should add extra skip arrays based on input keys)
*/
numArrayKeys = _bt_num_array_keys(scan, skip_eq_ops, &numSkipArrayKeys);
so->skipScan = (numSkipArrayKeys > 0);
/* Quit if nothing to do. */
if (numArrayKeys == 0)
return NULL;
/*
* Estimated final size of arrayKeyData[] array we'll return to our caller
* is the size of the original scan->keyData[] input array, plus space for
* any additional skip array scan keys we'll need to generate below
*/
numArrayKeyData = scan->numberOfKeys + numSkipArrayKeys;
/*
* 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_SIZES);
else
MemoryContextReset(so->arrayContext);
/* Create output scan keys in the workspace context */
arrayKeyData = (ScanKey) palloc(numArrayKeyData * sizeof(ScanKeyData));
/* Allocate space for per-array data in the workspace context */
so->arrayKeys = (BTArrayKeyInfo *) palloc(numArrayKeys * sizeof(BTArrayKeyInfo));
/* Allocate space for ORDER procs used to help _bt_checkkeys */
so->orderProcs = (FmgrInfo *) palloc(numArrayKeyData * sizeof(FmgrInfo));
numArrayKeys = 0;
numArrayKeyData = 0;
for (int input_ikey = 0; input_ikey < scan->numberOfKeys; input_ikey++)
{
ScanKey inkey = scan->keyData + input_ikey,
cur;
FmgrInfo sortproc;
FmgrInfo *sortprocp = &sortproc;
Oid elemtype;
bool reverse;
ArrayType *arrayval;
int16 elmlen;
bool elmbyval;
char elmalign;
int num_elems;
Datum *elem_values;
bool *elem_nulls;
int num_nonnulls;
/* set up next output scan key */
cur = &arrayKeyData[numArrayKeyData];
/* Backfill skip arrays for attrs < or <= input key's attr? */
while (numSkipArrayKeys && attno_skip <= inkey->sk_attno)
{
Oid opfamily = rel->rd_opfamily[attno_skip - 1];
Oid opcintype = rel->rd_opcintype[attno_skip - 1];
Oid collation = rel->rd_indcollation[attno_skip - 1];
Oid eq_op = skip_eq_ops[attno_skip - 1];
CompactAttribute *attr;
RegProcedure cmp_proc;
if (!OidIsValid(eq_op))
{
/*
* Attribute already has an = input key, so don't output a
* skip array for attno_skip. Just copy attribute's = input
* key into arrayKeyData[] once outside this inner loop.
*
* Note: When we get here there must be a later attribute that
* lacks an equality input key, and still needs a skip array
* (if there wasn't then numSkipArrayKeys would be 0 by now).
*/
Assert(attno_skip == inkey->sk_attno);
/* inkey can't be last input key to be marked required: */
Assert(input_ikey < scan->numberOfKeys - 1);
#if 0
/* Could be a redundant input scan key, so can't do this: */
Assert(inkey->sk_strategy == BTEqualStrategyNumber ||
(inkey->sk_flags & SK_SEARCHNULL));
#endif
attno_skip++;
break;
}
cmp_proc = get_opcode(eq_op);
if (!RegProcedureIsValid(cmp_proc))
elog(ERROR, "missing oprcode for skipping equals operator %u", eq_op);
/* If array is null as a whole, the scan qual is unsatisfiable */
if (cur->sk_flags & SK_ISNULL)
{
so->qual_ok = false;
break;
}
/*
* Deconstruct the array into elements
*/
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 (int 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)
{
so->qual_ok = false;
break;
}
/*
* 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 = cur->sk_subtype;
if (elemtype == InvalidOid)
elemtype = rel->rd_opcintype[cur->sk_attno - 1];
/*
* 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, elemtype,
BTGreaterStrategyNumber,
elem_values, num_nonnulls);
numArrayKeyData++; /* keep this transformed scan key */
continue;
case BTEqualStrategyNumber:
/* proceed with rest of loop */
break;
case BTGreaterEqualStrategyNumber:
case BTGreaterStrategyNumber:
cur->sk_argument =
_bt_find_extreme_element(scan, cur, elemtype,
BTLessStrategyNumber,
elem_values, num_nonnulls);
numArrayKeyData++; /* keep this transformed scan key */
continue;
default:
elog(ERROR, "unrecognized StrategyNumber: %d",
(int) cur->sk_strategy);
break;
}
/*
* We'll need a 3-way ORDER proc to perform binary searches for the
* next matching array element. Set that up now.
*
* Array scan keys with cross-type equality operators will require a
* separate same-type ORDER proc for sorting their array. Otherwise,
* sortproc just points to the same proc used during binary searches.
*/
_bt_setup_array_cmp(scan, cur, elemtype,
&so->orderProcs[numArrayKeyData], &sortprocp);
/*
* Sort the non-null elements and eliminate any duplicates. We must
* sort in the same ordering used by the index column, so that the
* arrays can be advanced in lockstep with the scan's progress through
* the index's key space.
*/
reverse = (indoption[cur->sk_attno - 1] & INDOPTION_DESC) != 0;
num_elems = _bt_sort_array_elements(cur, sortprocp, reverse,
elem_values, num_nonnulls);
if (origarrayatt == cur->sk_attno)
{
BTArrayKeyInfo *orig = &so->arrayKeys[origarraykey];
/*
* This array scan key is redundant with a previous equality
* operator array scan key. Merge the two arrays together to
* eliminate contradictory non-intersecting elements (or try to).
*
* We merge this next array back into attribute's original array.
*/
Assert(arrayKeyData[orig->scan_key].sk_attno == cur->sk_attno);
Assert(arrayKeyData[orig->scan_key].sk_collation ==
cur->sk_collation);
if (_bt_merge_arrays(scan, cur, sortprocp, reverse,
origelemtype, elemtype,
orig->elem_values, &orig->num_elems,
elem_values, num_elems))
{
/* Successfully eliminated this array */
pfree(elem_values);
/*
* If no intersecting elements remain in the original array,
* the scan qual is unsatisfiable
*/
if (orig->num_elems == 0)
{
so->qual_ok = false;
break;
}
/* Throw away this scan key/array */
continue;
}
/*
* Unable to merge this array with previous array due to a lack of
* suitable cross-type opfamily support. Will need to keep both
* scan keys/arrays.
*/
}
else
{
/*
* This array is the first for current index attribute.
*
* If it turns out to not be the last array (that is, if the next
* array is redundantly applied to this same index attribute),
* we'll then treat this array as the attribute's"original" array
* when merging.
*/
origarrayatt = cur->sk_attno;
origarraykey = numArrayKeys;
origelemtype = elemtype;
}
/* Initialize SAOP array specific BTArrayKeyInfo fields */
so->arrayKeys[numArrayKeys].elem_values = elem_values;
so->arrayKeys[numArrayKeys].cur_elem = -1; /* i.e. invalid */
numArrayKeys++;
numArrayKeyData++; /* keep this scan key/array */
}
Assert(numSkipArrayKeys == 0 || !so->qual_ok);
/* Set final number of equality-type array keys */
so->numArrayKeys = numArrayKeys;
/* Set number of scan keys in arrayKeyData[] */
*new_numberOfKeys = numArrayKeyData;
MemoryContextSwitchTo(oldContext);
return arrayKeyData;
}
/*
* _bt_preprocess_array_keys_final() -- fix up array scan key references
*
* When _bt_preprocess_array_keys performed initial array preprocessing, it
* set each array's array->scan_key to its scankey's arrayKeyData[] offset.
* This function handles translation of the scan key references from the
* BTArrayKeyInfo info array, from input scan key references (to the keys in
* arrayKeyData[]), into output references (to the keys in so->keyData[]).
* Caller's keyDataMap[] array tells us how to perform this remapping.
*
* Also finalizes so->orderProcs[] for the scan. Arrays already have an ORDER
* proc, which might need to be repositioned to its so->keyData[]-wise offset
* (very much like the remapping that we apply to array->scan_key references).
* Non-array equality strategy scan keys (that survived preprocessing) don't
* yet have an so->orderProcs[] entry, so we set one for them here.
*
* Also converts single-element array scan keys into equivalent non-array
* equality scan keys, which decrements so->numArrayKeys. It's possible that
* this will leave this new btrescan without any arrays at all. This isn't
* necessary for correctness; it's just an optimization. Non-array equality
* scan keys are slightly faster than equivalent array scan keys at runtime.
*/
static void
_bt_preprocess_array_keys_final(IndexScanDesc scan, int *keyDataMap)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
Relation rel = scan->indexRelation;
int arrayidx = 0;
int last_equal_output_ikey PG_USED_FOR_ASSERTS_ONLY = -1;
Assert(so->qual_ok);
/*
* Nothing for us to do when _bt_preprocess_array_keys only had to deal
* with array inequalities
*/
if (so->numArrayKeys == 0)
return;
for (int output_ikey = 0; output_ikey < so->numberOfKeys; output_ikey++)
{
ScanKey outkey = so->keyData + output_ikey;
int input_ikey;
bool found PG_USED_FOR_ASSERTS_ONLY = false;
Assert(outkey->sk_strategy != InvalidStrategy);
if (outkey->sk_strategy != BTEqualStrategyNumber)
continue;
/*
* We're lazy about looking up ORDER procs for non-array keys, since
* not all input keys become output keys. Take care of it now.
*/
if (!(outkey->sk_flags & SK_SEARCHARRAY))
{
Oid elemtype;
/* No need for an ORDER proc given an IS NULL scan key */
if (outkey->sk_flags & SK_SEARCHNULL)
continue;
/*
* A non-required scan key doesn't need an ORDER proc, either
* (unless it's associated with an array, which this one isn't)
*/
if (!(outkey->sk_flags & SK_BT_REQFWD))
continue;
/*
* Reorder existing array scan key so->orderProcs[] entries.
*
* Doing this in-place is safe because preprocessing is required to
* output all equality strategy scan keys in original input order
* (among each group of entries against the same index attribute).
* This is also the order that the arrays themselves appear in.
*/
so->orderProcs[output_ikey] = so->orderProcs[input_ikey];
/* Fix-up array->scan_key references for arrays */
for (; arrayidx < so->numArrayKeys; arrayidx++)
{
BTArrayKeyInfo *array = &so->arrayKeys[arrayidx];
/*
* All skip arrays must be marked required, and final column can
* never have a skip array
*/
Assert(array->num_elems > 0 || array->num_elems == -1);
Assert(array->num_elems != -1 || outkey->sk_flags & SK_BT_REQFWD);
Assert(array->num_elems != -1 ||
outkey->sk_attno < IndexRelationGetNumberOfKeyAttributes(rel));
if (array->scan_key == input_ikey)
{
/* found it */
array->scan_key = output_ikey;
found = true;
/*
* Transform array scan keys that have exactly 1 element
* remaining (following all prior preprocessing) into
* equivalent non-array scan keys.
*/
if (array->num_elems == 1)
{
outkey->sk_flags &= ~SK_SEARCHARRAY;
outkey->sk_argument = array->elem_values[0];
so->numArrayKeys--;
/* If we're out of array keys, we can quit right away */
if (so->numArrayKeys == 0)
return;
/*
* Don't increment arrayidx (there was an entry that was
* just shifted forward to the offset at arrayidx, which
* will still need to be matched)
*/
}
else
{
/*
* Any skip array low_compare and high_compare scan keys
* are now final. Transform the array's > low_compare key
* into a >= key (and < high_compare keys into a <= key).
*/
if (array->num_elems == -1 && array->sksup &&
!array->null_elem)
_bt_skiparray_strat_adjust(scan, outkey, array);
/* Match found, so done with this array */
arrayidx++;
}
break;
}
}
Assert(found);
}
/*
* Parallel index scans require space in shared memory to store the
* current array elements (for arrays kept by preprocessing) to schedule
* the next primitive index scan. The underlying structure is protected
* using an LWLock, so defensively limit its size. In practice this can
* only affect parallel scans that use an incomplete opfamily.
*/
if (scan->parallel_scan && so->numArrayKeys > INDEX_MAX_KEYS)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg_internal("number of array scan keys left by preprocessing (%d) exceeds the maximum allowed by parallel btree index scans (%d)",
so->numArrayKeys, INDEX_MAX_KEYS)));
}
/*
* _bt_num_array_keys() -- determine # of BTArrayKeyInfo entries
*
* _bt_preprocess_array_keys helper function. Returns the estimated size of
* the scan's BTArrayKeyInfo array, which is guaranteed to be large enough to
* fit every so->arrayKeys[] entry.
*
* Also sets *numSkipArrayKeys_out to the number of skip arrays caller must
* add to the scan keys it'll output. Caller must add this many skip arrays:
* one array for each of the most significant attributes that lack a = input
* key (IS NULL keys count as = input keys here). The specific attributes
* that need skip arrays are indicated by initializing skip_eq_ops_out[] arg
* 0-based attribute offset to a valid = op strategy Oid. We'll only ever set
* skip_eq_ops_out[] entries to InvalidOid for attributes that already have an
* equality key in scan->keyData[] input keys -- and only when there's some
* later "attribute gap" for us to "fill-in" with a skip array.
*
* We're optimistic about skipping working out: we always add exactly the skip
* arrays needed to maximize the number of input scan keys that can ultimately
* be marked as required to continue the scan (but no more). Given a
* multi-column index on (a, b, c, d), we add skip arrays as follows:
*
* Input keys Output keys (after all preprocessing)
* ---------- -------------------------------------
* a = 1a = 1 (no skip arrays)
* b = 42 skip a AND b = 42
* a = 1 AND b = 42a = 1 AND b = 42 (no skip arrays)
* a >= 1 AND b = 42 range skip a AND b = 42
* a = 1 AND b > 42a = 1 AND b > 42 (no skip arrays)
* a >= 1 AND a <= 3 AND b = 42 range skip a AND b = 42
* a = 1 AND c <= 27a = 1 AND skip b AND c <= 27
* a = 1 AND d >= 1a = 1 AND skip b AND skip c AND d >= 1
* a = 1 AND b >= 42 AND d > 1a = 1 AND range skip b AND skip c AND d > 1
*/
static int
_bt_num_array_keys(IndexScanDesc scan, Oid *skip_eq_ops_out,
int *numSkipArrayKeys_out)
{
Relation rel = scan->indexRelation;
AttrNumber attno_skip = 1,
attno_inkey = 1;
bool attno_has_equal = false,
attno_has_rowcompare = false;
int numSAOPArrayKeys,
numSkipArrayKeys,
prev_numSkipArrayKeys;
Assert(scan->numberOfKeys);
/* Initial pass over input scan keys counts the number of SAOP arrays */
numSAOPArrayKeys = 0;
*numSkipArrayKeys_out = prev_numSkipArrayKeys = numSkipArrayKeys = 0;
for (int i = 0; i < scan->numberOfKeys; i++)
{
ScanKey inkey = scan->keyData + i;
if (inkey->sk_flags & SK_SEARCHARRAY)
numSAOPArrayKeys++;
}
for (int i = 0;; i++)
{
ScanKey inkey = scan->keyData + i;
/*
* Backfill skip arrays for any wholly omitted attributes prior to
* attno_inkey
*/
while (attno_skip < attno_inkey)
{
Oid opfamily = rel->rd_opfamily[attno_skip - 1];
Oid opcintype = rel->rd_opcintype[attno_skip - 1];
/* Look up input opclass's equality operator (might fail) */
skip_eq_ops_out[attno_skip - 1] =
get_opfamily_member(opfamily, opcintype, opcintype,
BTEqualStrategyNumber);
if (!OidIsValid(skip_eq_ops_out[attno_skip - 1]))
{
/*
* Cannot generate a skip array for this or later attributes
* (input opclass lacks an equality strategy operator)
*/
*numSkipArrayKeys_out = prev_numSkipArrayKeys;
return numSAOPArrayKeys + prev_numSkipArrayKeys;
}
/* plan on adding a backfill skip array for this attribute */
numSkipArrayKeys++;
attno_skip++;
}
prev_numSkipArrayKeys = numSkipArrayKeys;
/*
* Stop once past the final input scan key. We deliberately never add
* a skip array for the last input scan key's attribute -- even when
* there are only inequality keys on that attribute.
*/
if (i == scan->numberOfKeys)
break;
/*
* Later preprocessing steps cannot merge a RowCompare into a skip
* array, so stop adding skip arrays once we see one. (Note that we
* can backfill skip arrays before a RowCompare, which will allow keys
* up to and including the RowCompare to be marked required.)
*
* Skip arrays work by maintaining a current array element value,
* which anchors lower-order keys via an implied equality constraint.
* This is incompatible with the current nbtree row comparison design,
* which compares all columns together, as an indivisible group.
* Alternative designs that can be used alongside skip arrays are
* possible, but it's not clear that they're really worth pursuing.
*
* A RowCompare qual "(a, b, c) > (10, 'foo', 42)" is equivalent to
* "(a=10 AND b='foo' AND c>42) OR (a=10 AND b>'foo') OR (a>10)".
* Decomposing this RowCompare into these 3 disjuncts allows each
* disjunct to be executed as a separate "single value" index scan.
* That'll give all 3 scans the ability to add skip arrays in the
* usual way (when there are any scalar keys after the RowCompare).
* Under this scheme, a qual "(a, b, c) > (10, 'foo', 42) AND d = 99"
* performs 3 separate scans, each of which can mark keys up to and
* including its "d = 99" key as required to continue the scan.
*/
if (attno_has_rowcompare)
break;
/*
* Now consider next attno_inkey (or keep going if this is an
* additional scan key against the same attribute)
*/
if (attno_inkey < inkey->sk_attno)
{
/*
* Now add skip array for previous scan key's attribute, though
* only if the attribute has no equality strategy scan keys
*/
if (attno_has_equal)
{
/* Attributes with an = key must have InvalidOid eq_op set */
skip_eq_ops_out[attno_skip - 1] = InvalidOid;
}
else
{
Oid opfamily = rel->rd_opfamily[attno_skip - 1];
Oid opcintype = rel->rd_opcintype[attno_skip - 1];
if (!OidIsValid(skip_eq_ops_out[attno_skip - 1]))
{
/*
* Input opclass lacks an equality strategy operator, so
* don't generate a skip array that definitely won't work
*/
break;
}
/* plan on adding a backfill skip array for this attribute */
numSkipArrayKeys++;
}
/* Set things up for this new attribute */
attno_skip++;
attno_inkey = inkey->sk_attno;
attno_has_equal = false;
}
/*
* Track if this attribute's scan keys include any equality strategy
* scan keys (IS NULL keys count as equality keys here). Also track
* if it has any RowCompare keys.
*/
if (inkey->sk_strategy == BTEqualStrategyNumber ||
(inkey->sk_flags & SK_SEARCHNULL))
attno_has_equal = true;
if (inkey->sk_flags & SK_ROW_HEADER)
attno_has_rowcompare = true;
}
/*
* _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, Oid elemtype,
StrategyNumber strat,
Datum *elems, int nelems)
{
Relation rel = scan->indexRelation;
Oid cmp_op;
RegProcedure cmp_proc;
FmgrInfo flinfo;
Datum result;
int i;
/*
* 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.
*/
Assert(skey->sk_strategy != BTEqualStrategyNumber);
Assert(OidIsValid(elemtype));
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_setup_array_cmp() -- Set up array comparison functions
*
* Sets ORDER proc in caller's orderproc argument, which is used during binary
* searches of arrays during the index scan. Also sets a same-type ORDER proc
* in caller's *sortprocp argument, which is used when sorting the array.
*
* Preprocessing calls here with all equality strategy scan keys (when scan
* uses equality array keys), including those not associated with any array.
* See _bt_advance_array_keys for an explanation of why it'll need to treat
* simple scalar equality scan keys as degenerate single element arrays.
*
* Caller should pass an orderproc pointing to space that'll store the ORDER
* proc for the scan, and a *sortprocp pointing to its own separate space.
* When calling here for a non-array scan key, sortprocp arg should be NULL.
*
* In the common case where we don't need to deal with cross-type operators,
* only one ORDER proc is actually required by caller. We'll set *sortprocp
* to point to the same memory that caller's orderproc continues to point to.
* Otherwise, *sortprocp will continue to point to caller's own space. Either
* way, *sortprocp will point to a same-type ORDER proc (since that's the only
* safe way to sort/deduplicate the array associated with caller's scan key).
*/
static void
_bt_setup_array_cmp(IndexScanDesc scan, ScanKey skey, Oid elemtype,
FmgrInfo *orderproc, FmgrInfo **sortprocp)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
Relation rel = scan->indexRelation;
RegProcedure cmp_proc;
Oid opcintype = rel->rd_opcintype[skey->sk_attno - 1];
/*
* If scankey operator is not a cross-type comparison, we can use the
* cached comparison function; otherwise gotta look it up in the catalogs
*/
if (elemtype == opcintype)
{
/* Set same-type ORDER procs for caller */
*orderproc = *index_getprocinfo(rel, skey->sk_attno, BTORDER_PROC);
if (sortprocp)
*sortprocp = orderproc;
return;
}
/*
* Look up the appropriate cross-type comparison function in the opfamily.
*
* Use the opclass input type as the left hand arg type, and the array
* element type as the right hand arg type (since binary searches use an
* index tuple's attribute value to search for a matching array element).
*
* Note: it's possible that this would fail, if the opfamily is
* incomplete, but only in cases where it's quite likely that _bt_first
* would fail in just the same way (had we not failed before it could).
*/
cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
opcintype, elemtype, BTORDER_PROC);
if (!RegProcedureIsValid(cmp_proc))
elog(ERROR, "missing support function %d(%u,%u) for attribute %d of index \"%s\"",
BTORDER_PROC, opcintype, elemtype, skey->sk_attno,
RelationGetRelationName(rel));
/* Set cross-type ORDER proc for caller */
fmgr_info_cxt(cmp_proc, orderproc, so->arrayContext);
/* Done if caller doesn't actually have an array they'll need to sort */
if (!sortprocp)
return;
/*
* Look up the appropriate same-type 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 comparison procs 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) for attribute %d of index \"%s\"",
BTORDER_PROC, elemtype, elemtype,
skey->sk_attno, RelationGetRelationName(rel));
/* Set same-type ORDER proc for caller */
fmgr_info_cxt(cmp_proc, *sortprocp, so->arrayContext);
}
/*
* _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.
*
* skey identifies the index column whose opfamily determines the comparison
* semantics, and sortproc is a corresponding ORDER proc. If reverse is true,
* we sort in descending order.
*/
static int
_bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc, bool reverse,
Datum *elems, int nelems)
{
BTSortArrayContext cxt;
if (nelems <= 1)
return nelems; /* no work to do */
/* Sort the array elements */
cxt.sortproc = sortproc;
cxt.collation = skey->sk_collation;
cxt.reverse = reverse;
qsort_arg(elems, nelems, sizeof(Datum),
_bt_compare_array_elements, &cxt);
/* Now scan the sorted elements and remove duplicates */
return qunique_arg(elems, nelems, sizeof(Datum),
_bt_compare_array_elements, &cxt);
}
/*
* _bt_merge_arrays() -- merge next array's elements into an original array
*
* Called when preprocessing encounters a pair of array equality scan keys,
* both against the same index attribute (during initial array preprocessing).
* Merging reorganizes caller's original array (the left hand arg) in-place,
* without ever copying elements from one array into the other. (Mixing the
* elements together like this would be wrong, since they don't necessarily
* use the same underlying element type, despite all the other similarities.)
*
* Both arrays must have already been sorted and deduplicated by calling
* _bt_sort_array_elements. sortproc is the same-type ORDER proc that was
* just used to sort and deduplicate caller's "next" array. We'll usually be
* able to reuse that order PROC to merge the arrays together now. If not,
* then we'll perform a separate ORDER proc lookup.
*
* If the opfamily doesn't supply a complete set of cross-type ORDER procs we
* may not be able to determine which elements are contradictory. If we have
* the required ORDER proc then we return true (and validly set *nelems_orig),
* guaranteeing that at least the next array can be considered redundant. We
* return false if the required comparisons cannot be made (caller must keep
* both arrays when this happens).
*/
static bool
_bt_merge_arrays(IndexScanDesc scan, ScanKey skey, FmgrInfo *sortproc,
bool reverse, Oid origelemtype, Oid nextelemtype,
Datum *elems_orig, int *nelems_orig,
Datum *elems_next, int nelems_next)
{
Relation rel = scan->indexRelation;
BTScanOpaque so = (BTScanOpaque) scan->opaque;
BTSortArrayContext cxt;
int nelems_orig_start = *nelems_orig,
nelems_orig_merged = 0;
FmgrInfo *mergeproc = sortproc;
FmgrInfo crosstypeproc;
if (origelemtype != nextelemtype)
{
RegProcedure cmp_proc;
/*
* Cross-array-element-type merging is required, so can't just reuse
* sortproc when merging
*/
cmp_proc = get_opfamily_proc(rel->rd_opfamily[skey->sk_attno - 1],
origelemtype, nextelemtype, BTORDER_PROC);
if (!RegProcedureIsValid(cmp_proc))
{
/* Can't make the required comparisons */
return false;
}
/* We have all we need to determine redundancy/contradictoriness */
mergeproc = &crosstypeproc;
fmgr_info_cxt(cmp_proc, mergeproc, so->arrayContext);
}
for (int i = 0, j = 0; i < nelems_orig_start && j < nelems_next;)
{
Datum *oelem = elems_orig + i,
*nelem = elems_next + j;
int res = _bt_compare_array_elements(oelem, nelem, &cxt);
if (res == 0)
{
elems_orig[nelems_orig_merged++] = *oelem; i++;
j++;
}
else if (res < 0) i++;
else /* res > 0 */
j++;
}
*nelems_orig = nelems_orig_merged;
return true;
}
/*
* 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->sortproc,
cxt->collation,
da, db));
if (cxt->reverse)
INVERT_COMPARE_RESULT(compare);
return compare;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.115 Sekunden
(vorverarbeitet am 2026-10-11)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.