mirror of
https://github.com/postgres/postgres.git
synced 2025-11-09 06:21:09 +03:00
The TimescaleDB extension expects to be able to change an nbtree scan's
keys across rescans. The issue arises in the extension's implementation
of loose index scan. This is arguably a misuse of the index AM API,
though apparently it worked until recently. It stopped working when the
skipScan flag was added to BTScanOpaqueData by commit 8a510275, though.
The flag wouldn't reliably track whether the scan (actually, the current
rescan) has any skip arrays, leading to confusion in _bt_set_startikey.
nbtree preprocessing will now defensively initialize the scan's skipScan
flag in all cases, including the case where _bt_preprocess_array_keys
returns early due to the (re)scan not using arrays. While nbtree isn't
obligated to support this use case (at least not according to my reading
of the index AM API), it still seems like a good idea to be consistent
here, on general robustness grounds.
Author: Peter Geoghegan <pg@bowt.ie>
Reported-By: Natalya Aksman <natalya@timescale.com>
Discussion: https://postgr.es/m/CAJumhcirfMojbk20+W0YimbNDkwdECvJprQGQ-XqK--ph09nQw@mail.gmail.com
Backpatch-through: 18
2849 lines
96 KiB
C
2849 lines
96 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nbtpreprocesskeys.c
|
|
* Preprocessing for Postgres btree scan keys.
|
|
*
|
|
* Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/nbtree/nbtpreprocesskeys.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/nbtree.h"
|
|
#include "access/relscan.h"
|
|
#include "common/int.h"
|
|
#include "lib/qunique.h"
|
|
#include "utils/array.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
|
|
typedef struct BTScanKeyPreproc
|
|
{
|
|
ScanKey inkey;
|
|
int inkeyi;
|
|
int arrayidx;
|
|
} BTScanKeyPreproc;
|
|
|
|
typedef struct BTSortArrayContext
|
|
{
|
|
FmgrInfo *sortproc;
|
|
Oid collation;
|
|
bool reverse;
|
|
} BTSortArrayContext;
|
|
|
|
static bool _bt_fix_scankey_strategy(ScanKey skey, int16 *indoption);
|
|
static void _bt_mark_scankey_required(ScanKey skey);
|
|
static bool _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
|
|
ScanKey leftarg, ScanKey rightarg,
|
|
BTArrayKeyInfo *array, FmgrInfo *orderproc,
|
|
bool *result);
|
|
static bool _bt_compare_array_scankey_args(IndexScanDesc scan,
|
|
ScanKey arraysk, ScanKey skey,
|
|
FmgrInfo *orderproc, BTArrayKeyInfo *array,
|
|
bool *qual_ok);
|
|
static bool _bt_saoparray_shrink(IndexScanDesc scan, ScanKey arraysk,
|
|
ScanKey skey, FmgrInfo *orderproc,
|
|
BTArrayKeyInfo *array, bool *qual_ok);
|
|
static bool _bt_skiparray_shrink(IndexScanDesc scan, ScanKey skey,
|
|
BTArrayKeyInfo *array, bool *qual_ok);
|
|
static void _bt_skiparray_strat_adjust(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array);
|
|
static void _bt_skiparray_strat_decrement(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array);
|
|
static void _bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array);
|
|
static void _bt_unmark_keys(IndexScanDesc scan, int *keyDataMap);
|
|
static int _bt_reorder_array_cmp(const void *a, const void *b);
|
|
static ScanKey _bt_preprocess_array_keys(IndexScanDesc scan, int *new_numberOfKeys);
|
|
static void _bt_preprocess_array_keys_final(IndexScanDesc scan, int *keyDataMap);
|
|
static int _bt_num_array_keys(IndexScanDesc scan, Oid *skip_eq_ops_out,
|
|
int *numSkipArrayKeys_out);
|
|
static Datum _bt_find_extreme_element(IndexScanDesc scan, ScanKey skey,
|
|
Oid elemtype, StrategyNumber strat,
|
|
Datum *elems, int nelems);
|
|
static void _bt_setup_array_cmp(IndexScanDesc scan, ScanKey skey, Oid elemtype,
|
|
FmgrInfo *orderproc, FmgrInfo **sortprocp);
|
|
static int _bt_sort_array_elements(ScanKey skey, FmgrInfo *sortproc,
|
|
bool reverse, Datum *elems, int nelems);
|
|
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);
|
|
static int _bt_compare_array_elements(const void *a, const void *b, void *arg);
|
|
|
|
|
|
/*
|
|
* _bt_preprocess_keys() -- Preprocess scan keys
|
|
*
|
|
* The given search-type keys (taken from 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. Calling here a second or subsequent time
|
|
* (during the same btrescan) is a no-op.
|
|
*
|
|
* The output keys are marked with additional sk_flags bits beyond the
|
|
* system-standard bits supplied by the caller. The DESC and NULLS_FIRST
|
|
* indoption bits for the relevant index attribute are copied into the flags.
|
|
* Also, for a DESC column, we commute (flip) all the sk_strategy numbers
|
|
* so that the index sorts in the desired direction.
|
|
*
|
|
* One key purpose of this routine is to discover which scan keys must be
|
|
* satisfied to continue the scan. It also attempts to eliminate redundant
|
|
* keys and detect contradictory keys. (If the index opfamily provides
|
|
* incomplete sets of cross-type operators, we may fail to detect redundant
|
|
* or contradictory keys, but we can survive that.)
|
|
*
|
|
* Required output keys are sorted by index attribute. Presently we expect
|
|
* (but verify) that the input keys are already so sorted --- this is done
|
|
* by match_clauses_to_index() in indxpath.c. Some reordering of the keys
|
|
* within each attribute may be done as a byproduct of the processing here.
|
|
* That process must leave array scan keys (within an attribute) in the same
|
|
* order as corresponding entries from the scan's BTArrayKeyInfo array info.
|
|
* We might also construct skip array scan keys that weren't present in the
|
|
* original input keys; these are also output in standard attribute order.
|
|
*
|
|
* The output keys are marked with flags SK_BT_REQFWD and/or SK_BT_REQBKWD
|
|
* if they must be satisfied in order to continue the scan forward or backward
|
|
* respectively. _bt_checkkeys uses these flags. For example, if the quals
|
|
* are "x = 1 AND y < 4 AND z < 5", then _bt_checkkeys will reject a tuple
|
|
* (1,2,7), but we must continue the scan in case there are tuples (1,3,z).
|
|
* But once we reach tuples like (1,4,z) we can stop scanning because no
|
|
* later tuples could match. This is reflected by marking the x and y keys,
|
|
* but not the z key, with SK_BT_REQFWD. In general, the keys for leading
|
|
* attributes with "=" keys are marked both SK_BT_REQFWD and SK_BT_REQBKWD.
|
|
* For the first attribute without an "=" key, any "<" and "<=" keys are
|
|
* marked SK_BT_REQFWD while any ">" and ">=" keys are marked SK_BT_REQBKWD.
|
|
* This can be seen to be correct by considering the above example.
|
|
*
|
|
* If we never generated skip array scan keys, it would be possible for "gaps"
|
|
* to appear that make it unsafe to mark any subsequent input scan keys
|
|
* (copied from scan->keyData[]) as required to continue the scan. Prior to
|
|
* Postgres 18, a qual like "WHERE y = 4" always resulted in a full scan.
|
|
* This qual now becomes "WHERE x = ANY('{every possible x value}') and y = 4"
|
|
* on output. In other words, preprocessing now adds a skip array on "x".
|
|
* This has the potential to be much more efficient than a full index scan
|
|
* (though it behaves like a full scan when there's many distinct "x" values).
|
|
*
|
|
* Typically, redundant keys are eliminated: we keep only the tightest
|
|
* >/>= bound and the tightest </<= bound, and if there's an = key then
|
|
* that's the only one returned. (So, we return either a single = key,
|
|
* or one or two boundary-condition keys for each attr.) However, if we
|
|
* cannot compare two keys for lack of a suitable cross-type operator,
|
|
* we cannot eliminate either key.
|
|
*
|
|
* When all redundant keys could not be eliminated, we'll output a key array
|
|
* that can more or less be treated as if it had no redundant keys. Suppose
|
|
* we have "x > 4::int AND x > 10::bigint AND x < 70", and we are unable to
|
|
* determine which > key is more restrictive for lack of a suitable cross-type
|
|
* operator. We'll arbitrarily pick one of the > keys; the other > key won't
|
|
* be marked required. Obviously, the scan will be less efficient if we
|
|
* choose x > 4 over x > 10 -- but it can still largely proceed as if there
|
|
* was only a single > condition. "x > 10" will be placed at the end of the
|
|
* so->keyData[] output array. It'll always be evaluated last, after the keys
|
|
* that could be marked required in the usual way (after "x > 4 AND x < 70").
|
|
* This can sometimes result in so->keyData[] keys that aren't even in index
|
|
* attribute order (if the qual involves multiple attributes). The scan's
|
|
* required keys will still be in attribute order, though, so it can't matter.
|
|
*
|
|
* This scheme ensures that _bt_first always uses the same set of keys at the
|
|
* start of a forwards scan as those _bt_checkkeys uses to determine when to
|
|
* end a similar backwards scan (and vice-versa). _bt_advance_array_keys
|
|
* depends on this: it expects to be able to reliably predict what the next
|
|
* _bt_first call will do by testing whether _bt_checkkeys' routines report
|
|
* that the final tuple on the page is past the end of matches for the scan's
|
|
* keys with the scan direction flipped. If it is (if continuescan=false),
|
|
* then it follows that calling _bt_first will, at a minimum, relocate the
|
|
* scan to the very next leaf page (in the current scan direction).
|
|
*
|
|
* As a byproduct of this work, we can detect contradictory quals such
|
|
* as "x = 1 AND x > 2". If we see that, we return so->qual_ok = false,
|
|
* indicating the scan need not be run at all since no tuples can match.
|
|
* (In this case we do not bother completing the output key array!)
|
|
* Again, missing cross-type operators might cause us to fail to prove the
|
|
* quals contradictory when they really are, but the scan will work correctly.
|
|
*
|
|
* Skip array = keys will even be generated in the presence of "contradictory"
|
|
* inequality quals when it'll enable marking later input quals as required.
|
|
* We'll merge any such inequalities into the generated skip array by setting
|
|
* its array.low_compare or array.high_compare key field. The resulting skip
|
|
* array will generate its array elements from a range that's constrained by
|
|
* any merged input inequalities (which won't get output in so->keyData[]).
|
|
*
|
|
* Row comparison keys currently have a couple of notable limitations.
|
|
* Right now we just transfer them into the preprocessed array without any
|
|
* editorialization. We can treat them the same as an ordinary inequality
|
|
* comparison on the row's first index column, for the purposes of the logic
|
|
* about required keys. Also, we are unable to merge a row comparison key
|
|
* into a skip array (only ordinary inequalities are merged). A key that
|
|
* comes after a Row comparison key is therefore never marked as required.
|
|
*
|
|
* 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 source data.
|
|
*/
|
|
void
|
|
_bt_preprocess_keys(IndexScanDesc scan)
|
|
{
|
|
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
|
int numberOfKeys = scan->numberOfKeys;
|
|
int16 *indoption = scan->indexRelation->rd_indoption;
|
|
int new_numberOfKeys;
|
|
int numberOfEqualCols;
|
|
ScanKey inkeys;
|
|
BTScanKeyPreproc xform[BTMaxStrategyNumber];
|
|
bool test_result,
|
|
redundant_key_kept = false;
|
|
AttrNumber attno;
|
|
ScanKey arrayKeyData;
|
|
int *keyDataMap = NULL;
|
|
int arrayidx = 0;
|
|
|
|
if (so->numberOfKeys > 0)
|
|
{
|
|
/*
|
|
* Only need to do preprocessing once per btrescan, at most. All
|
|
* calls after the first are handled as no-ops.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/* initialize result variables */
|
|
so->qual_ok = true;
|
|
so->numberOfKeys = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Treat arrayKeyData[] (a partially preprocessed copy of scan->keyData[])
|
|
* as our input if _bt_preprocess_array_keys just allocated it, else just
|
|
* use scan->keyData[]
|
|
*/
|
|
if (arrayKeyData)
|
|
{
|
|
inkeys = arrayKeyData;
|
|
|
|
/* Also maintain keyDataMap for remapping so->orderProcs[] later */
|
|
keyDataMap = MemoryContextAlloc(so->arrayContext,
|
|
numberOfKeys * sizeof(int));
|
|
|
|
/*
|
|
* Also enlarge output array when it might otherwise not have room for
|
|
* a skip array's scan key
|
|
*/
|
|
if (numberOfKeys > scan->numberOfKeys)
|
|
so->keyData = repalloc(so->keyData,
|
|
numberOfKeys * sizeof(ScanKeyData));
|
|
}
|
|
else
|
|
inkeys = scan->keyData;
|
|
|
|
/* 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't call _bt_preprocess_array_keys_final in this fast path
|
|
* (we'll miss out on the single value array transformation, but
|
|
* that's not nearly as important when there's only one scan key)
|
|
*/
|
|
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)));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, do the full set of pushups.
|
|
*/
|
|
new_numberOfKeys = 0;
|
|
numberOfEqualCols = 0;
|
|
|
|
/*
|
|
* Initialize for processing of keys for attr 1.
|
|
*
|
|
* xform[i] points to the currently best scan key of strategy type i+1; it
|
|
* is NULL if we haven't yet found such a key for this attr.
|
|
*/
|
|
attno = 1;
|
|
memset(xform, 0, sizeof(xform));
|
|
|
|
/*
|
|
* Loop iterates from 0 to numberOfKeys inclusive; we use the last pass to
|
|
* handle after-last-key processing. Actual exit from the loop is at the
|
|
* "break" statement below.
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we are at the end of the keys for a particular attr, finish up
|
|
* processing and emit the cleaned-up keys.
|
|
*/
|
|
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");
|
|
|
|
/*
|
|
* If = has been specified, all other keys can be eliminated as
|
|
* redundant. Note that this is no less true if the = key is
|
|
* SEARCHARRAY; the only real difference is that the inequality
|
|
* key _becomes_ redundant by making _bt_compare_scankey_args
|
|
* eliminate the subset of elements that won't need to be matched
|
|
* (with SAOP arrays and skip arrays alike).
|
|
*
|
|
* If we have a case like "key = 1 AND key > 2", we set qual_ok to
|
|
* false and abandon further processing. We'll do the same thing
|
|
* given a case like "key IN (0, 1) AND key > 2".
|
|
*
|
|
* We also have to deal with the case of "key IS NULL", which is
|
|
* unsatisfiable in combination with any other index condition. By
|
|
* the time we get here, that's been classified as an equality
|
|
* check, and we've rejected any combination of it with a regular
|
|
* equality condition; but not with other types of conditions.
|
|
*/
|
|
if (xform[BTEqualStrategyNumber - 1].inkey)
|
|
{
|
|
ScanKey eq = xform[BTEqualStrategyNumber - 1].inkey;
|
|
BTArrayKeyInfo *array = NULL;
|
|
FmgrInfo *orderproc = NULL;
|
|
|
|
if (arrayKeyData && (eq->sk_flags & SK_SEARCHARRAY))
|
|
{
|
|
int eq_in_ikey,
|
|
eq_arrayidx;
|
|
|
|
eq_in_ikey = xform[BTEqualStrategyNumber - 1].inkeyi;
|
|
eq_arrayidx = xform[BTEqualStrategyNumber - 1].arrayidx;
|
|
array = &so->arrayKeys[eq_arrayidx - 1];
|
|
orderproc = so->orderProcs + eq_in_ikey;
|
|
|
|
Assert(array->scan_key == eq_in_ikey);
|
|
Assert(OidIsValid(orderproc->fn_oid));
|
|
}
|
|
|
|
for (j = BTMaxStrategyNumber; --j >= 0;)
|
|
{
|
|
ScanKey chk = xform[j].inkey;
|
|
|
|
if (!chk || j == (BTEqualStrategyNumber - 1))
|
|
continue;
|
|
|
|
if (eq->sk_flags & SK_SEARCHNULL)
|
|
{
|
|
/* IS NULL is contradictory to anything else */
|
|
so->qual_ok = false;
|
|
return;
|
|
}
|
|
|
|
if (_bt_compare_scankey_args(scan, chk, eq, chk,
|
|
array, orderproc,
|
|
&test_result))
|
|
{
|
|
if (!test_result)
|
|
{
|
|
/* keys proven mutually contradictory */
|
|
so->qual_ok = false;
|
|
return;
|
|
}
|
|
/* else discard the redundant non-equality key */
|
|
xform[j].inkey = NULL;
|
|
xform[j].inkeyi = -1;
|
|
}
|
|
else
|
|
redundant_key_kept = true;
|
|
}
|
|
/* track number of attrs for which we have "=" keys */
|
|
numberOfEqualCols++;
|
|
}
|
|
|
|
/* try to keep only one of <, <= */
|
|
if (xform[BTLessStrategyNumber - 1].inkey &&
|
|
xform[BTLessEqualStrategyNumber - 1].inkey)
|
|
{
|
|
ScanKey lt = xform[BTLessStrategyNumber - 1].inkey;
|
|
ScanKey le = xform[BTLessEqualStrategyNumber - 1].inkey;
|
|
|
|
if (_bt_compare_scankey_args(scan, le, lt, le, NULL, NULL,
|
|
&test_result))
|
|
{
|
|
if (test_result)
|
|
xform[BTLessEqualStrategyNumber - 1].inkey = NULL;
|
|
else
|
|
xform[BTLessStrategyNumber - 1].inkey = NULL;
|
|
}
|
|
else
|
|
redundant_key_kept = true;
|
|
}
|
|
|
|
/* try to keep only one of >, >= */
|
|
if (xform[BTGreaterStrategyNumber - 1].inkey &&
|
|
xform[BTGreaterEqualStrategyNumber - 1].inkey)
|
|
{
|
|
ScanKey gt = xform[BTGreaterStrategyNumber - 1].inkey;
|
|
ScanKey ge = xform[BTGreaterEqualStrategyNumber - 1].inkey;
|
|
|
|
if (_bt_compare_scankey_args(scan, ge, gt, ge, NULL, NULL,
|
|
&test_result))
|
|
{
|
|
if (test_result)
|
|
xform[BTGreaterEqualStrategyNumber - 1].inkey = NULL;
|
|
else
|
|
xform[BTGreaterStrategyNumber - 1].inkey = NULL;
|
|
}
|
|
else
|
|
redundant_key_kept = true;
|
|
}
|
|
|
|
/*
|
|
* Emit the cleaned-up keys into the so->keyData[] array, and then
|
|
* mark them if they are required. They are required (possibly
|
|
* only in one direction) if all attrs before this one had "=".
|
|
*
|
|
* In practice we'll rarely output non-required scan keys here;
|
|
* typically, _bt_preprocess_array_keys has already added "=" keys
|
|
* sufficient to form an unbroken series of "=" constraints on all
|
|
* attrs prior to the attr from the final scan->keyData[] key.
|
|
*/
|
|
for (j = BTMaxStrategyNumber; --j >= 0;)
|
|
{
|
|
if (xform[j].inkey)
|
|
{
|
|
ScanKey outkey = &so->keyData[new_numberOfKeys++];
|
|
|
|
memcpy(outkey, xform[j].inkey, sizeof(ScanKeyData));
|
|
if (arrayKeyData)
|
|
keyDataMap[new_numberOfKeys - 1] = xform[j].inkeyi;
|
|
if (priorNumberOfEqualCols == attno - 1)
|
|
_bt_mark_scankey_required(outkey);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Exit loop here if done.
|
|
*/
|
|
if (i == numberOfKeys)
|
|
break;
|
|
|
|
/* Re-initialize for new attno */
|
|
attno = inkey->sk_attno;
|
|
memset(xform, 0, sizeof(xform));
|
|
}
|
|
|
|
/* check strategy this key's operator corresponds to */
|
|
j = inkey->sk_strategy - 1;
|
|
|
|
if (inkey->sk_strategy == BTEqualStrategyNumber &&
|
|
(inkey->sk_flags & SK_SEARCHARRAY))
|
|
{
|
|
/* must track how input scan keys map to arrays */
|
|
Assert(arrayKeyData);
|
|
arrayidx++;
|
|
}
|
|
|
|
/*
|
|
* have we seen a scan key for this same attribute and using this same
|
|
* operator strategy before now?
|
|
*/
|
|
if (xform[j].inkey == NULL)
|
|
{
|
|
/* nope, so this scan key wins by default (at least for now) */
|
|
xform[j].inkey = inkey;
|
|
xform[j].inkeyi = i;
|
|
xform[j].arrayidx = arrayidx;
|
|
}
|
|
else
|
|
{
|
|
FmgrInfo *orderproc = NULL;
|
|
BTArrayKeyInfo *array = NULL;
|
|
|
|
/*
|
|
* Seen one of these before, so keep only the more restrictive key
|
|
* if possible
|
|
*/
|
|
if (j == (BTEqualStrategyNumber - 1) && arrayKeyData)
|
|
{
|
|
/*
|
|
* Have to set up array keys
|
|
*/
|
|
if (inkey->sk_flags & SK_SEARCHARRAY)
|
|
{
|
|
array = &so->arrayKeys[arrayidx - 1];
|
|
orderproc = so->orderProcs + i;
|
|
|
|
Assert(array->scan_key == i);
|
|
Assert(OidIsValid(orderproc->fn_oid));
|
|
Assert(!(inkey->sk_flags & SK_BT_SKIP));
|
|
}
|
|
else if (xform[j].inkey->sk_flags & SK_SEARCHARRAY)
|
|
{
|
|
array = &so->arrayKeys[xform[j].arrayidx - 1];
|
|
orderproc = so->orderProcs + xform[j].inkeyi;
|
|
|
|
Assert(array->scan_key == xform[j].inkeyi);
|
|
Assert(OidIsValid(orderproc->fn_oid));
|
|
Assert(!(xform[j].inkey->sk_flags & SK_BT_SKIP));
|
|
}
|
|
|
|
/*
|
|
* Both scan keys might have arrays, in which case we'll
|
|
* arbitrarily pass only one of the arrays. That won't
|
|
* matter, since _bt_compare_scankey_args is aware that two
|
|
* SEARCHARRAY scan keys mean that _bt_preprocess_array_keys
|
|
* failed to eliminate redundant arrays through array merging.
|
|
* _bt_compare_scankey_args just returns false when it sees
|
|
* this; it won't even try to examine either array.
|
|
*/
|
|
}
|
|
|
|
if (_bt_compare_scankey_args(scan, inkey, inkey, xform[j].inkey,
|
|
array, orderproc, &test_result))
|
|
{
|
|
/* Have all we need to determine redundancy */
|
|
if (test_result)
|
|
{
|
|
/*
|
|
* New key is more restrictive, and so replaces old key...
|
|
*/
|
|
if (j != (BTEqualStrategyNumber - 1) ||
|
|
!(xform[j].inkey->sk_flags & SK_SEARCHARRAY))
|
|
{
|
|
xform[j].inkey = inkey;
|
|
xform[j].inkeyi = i;
|
|
xform[j].arrayidx = arrayidx;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* ...unless we have to keep the old key because it's
|
|
* an array that rendered the new key redundant. We
|
|
* need to make sure that we don't throw away an array
|
|
* scan key. _bt_preprocess_array_keys_final expects
|
|
* us to keep all of the arrays that weren't already
|
|
* eliminated by _bt_preprocess_array_keys earlier on.
|
|
*/
|
|
Assert(!(inkey->sk_flags & SK_SEARCHARRAY));
|
|
}
|
|
}
|
|
else if (j == (BTEqualStrategyNumber - 1))
|
|
{
|
|
/* key == a && key == b, but a != b */
|
|
so->qual_ok = false;
|
|
return;
|
|
}
|
|
/* else old key is more restrictive, keep it */
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We can't determine which key is more restrictive. Push
|
|
* xform[j] directly to the output array, then set xform[j] to
|
|
* the new scan key.
|
|
*
|
|
* Note: We do things this way around so that our arrays are
|
|
* always in the same order as their corresponding scan keys.
|
|
* _bt_preprocess_array_keys_final expects this.
|
|
*/
|
|
ScanKey outkey = &so->keyData[new_numberOfKeys++];
|
|
|
|
memcpy(outkey, xform[j].inkey, sizeof(ScanKeyData));
|
|
if (arrayKeyData)
|
|
keyDataMap[new_numberOfKeys - 1] = xform[j].inkeyi;
|
|
if (numberOfEqualCols == attno - 1)
|
|
_bt_mark_scankey_required(outkey);
|
|
xform[j].inkey = inkey;
|
|
xform[j].inkeyi = i;
|
|
xform[j].arrayidx = arrayidx;
|
|
redundant_key_kept = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
so->numberOfKeys = new_numberOfKeys;
|
|
|
|
/*
|
|
* Now that we've built a temporary mapping from so->keyData[] (output
|
|
* scan keys) to arrayKeyData[] (our input scan keys), fix array->scan_key
|
|
* references. Also consolidate the so->orderProcs[] array such that it
|
|
* can be subscripted using so->keyData[]-wise offsets.
|
|
*/
|
|
if (arrayKeyData)
|
|
_bt_preprocess_array_keys_final(scan, keyDataMap);
|
|
|
|
/*
|
|
* If there are remaining redundant inequality keys, we must make sure
|
|
* that each index attribute has no more than one required >/>= key, and
|
|
* no more than one required </<= key. Attributes that have one or more
|
|
* required = keys now must keep only one required key (the first = key).
|
|
*/
|
|
if (unlikely(redundant_key_kept) && so->qual_ok)
|
|
_bt_unmark_keys(scan, keyDataMap);
|
|
|
|
/* Could pfree arrayKeyData/keyDataMap now, but not worth the cycles */
|
|
}
|
|
|
|
/*
|
|
* Adjust a scankey's strategy and flags setting as needed for indoptions.
|
|
*
|
|
* We copy the appropriate indoption value into the scankey sk_flags
|
|
* (shifting to avoid clobbering system-defined flag bits). Also, if
|
|
* the DESC option is set, commute (flip) the operator strategy number.
|
|
*
|
|
* A secondary purpose is to check for IS NULL/NOT NULL scankeys and set up
|
|
* the strategy field correctly for them.
|
|
*
|
|
* Lastly, for ordinary scankeys (not IS NULL/NOT NULL), we check for a
|
|
* NULL comparison value. Since all btree operators are assumed strict,
|
|
* a NULL means that the qual cannot be satisfied. We return true if the
|
|
* comparison value isn't NULL, or false if the scan should be abandoned.
|
|
*
|
|
* This function is applied to the *input* scankey structure; therefore
|
|
* on a rescan we will be looking at already-processed scankeys. Hence
|
|
* we have to be careful not to re-commute the strategy if we already did it.
|
|
* It's a bit ugly to modify the caller's copy of the scankey but in practice
|
|
* there shouldn't be any problem, since the index's indoptions are certainly
|
|
* not going to change while the scankey survives.
|
|
*/
|
|
static bool
|
|
_bt_fix_scankey_strategy(ScanKey skey, int16 *indoption)
|
|
{
|
|
int addflags;
|
|
|
|
addflags = indoption[skey->sk_attno - 1] << SK_BT_INDOPTION_SHIFT;
|
|
|
|
/*
|
|
* We treat all btree operators as strict (even if they're not so marked
|
|
* in pg_proc). This means that it is impossible for an operator condition
|
|
* with a NULL comparison constant to succeed, and we can reject it right
|
|
* away.
|
|
*
|
|
* However, we now also support "x IS NULL" clauses as search conditions,
|
|
* so in that case keep going. The planner has not filled in any
|
|
* particular strategy in this case, so set it to BTEqualStrategyNumber
|
|
* --- we can treat IS NULL as an equality operator for purposes of search
|
|
* strategy.
|
|
*
|
|
* Likewise, "x IS NOT NULL" is supported. We treat that as either "less
|
|
* than NULL" in a NULLS LAST index, or "greater than NULL" in a NULLS
|
|
* FIRST index.
|
|
*
|
|
* Note: someday we might have to fill in sk_collation from the index
|
|
* column's collation. At the moment this is a non-issue because we'll
|
|
* never actually call the comparison operator on a NULL.
|
|
*/
|
|
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;
|
|
}
|
|
else if (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 */
|
|
return false;
|
|
}
|
|
|
|
/* Needn't do the rest */
|
|
return true;
|
|
}
|
|
|
|
/* 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);
|
|
return false;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
|
addflags = indoption[subkey->sk_attno - 1] << SK_BT_INDOPTION_SHIFT;
|
|
if ((addflags & SK_BT_DESC) && !(subkey->sk_flags & SK_BT_DESC))
|
|
subkey->sk_strategy = BTCommuteStrategyNumber(subkey->sk_strategy);
|
|
subkey->sk_flags |= addflags;
|
|
if (subkey->sk_flags & SK_ROW_END)
|
|
break;
|
|
subkey++;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Mark a scankey as "required to continue the scan".
|
|
*
|
|
* Depending on the operator type, the key may be required for both scan
|
|
* directions or just one. Also, if the key is a row comparison header,
|
|
* we have to mark the appropriate subsidiary ScanKeys as required. In such
|
|
* cases, the first subsidiary key is required, but subsequent ones are
|
|
* required only as long as they correspond to successive index columns and
|
|
* match the leading column as to sort direction. Otherwise the row
|
|
* comparison ordering is different from the index ordering and so we can't
|
|
* stop the scan on the basis of those lower-order columns.
|
|
*
|
|
* Note: when we set required-key flag bits in a subsidiary scankey, we are
|
|
* scribbling on a data structure belonging to the index AM's caller, not on
|
|
* our private copy. This should be OK because the marking will not change
|
|
* from scan to scan within a query, and so we'd just re-mark the same way
|
|
* anyway on a rescan. Something to keep an eye on though.
|
|
*/
|
|
static void
|
|
_bt_mark_scankey_required(ScanKey skey)
|
|
{
|
|
int addflags;
|
|
|
|
switch (skey->sk_strategy)
|
|
{
|
|
case BTLessStrategyNumber:
|
|
case BTLessEqualStrategyNumber:
|
|
addflags = SK_BT_REQFWD;
|
|
break;
|
|
case BTEqualStrategyNumber:
|
|
addflags = SK_BT_REQFWD | SK_BT_REQBKWD;
|
|
break;
|
|
case BTGreaterEqualStrategyNumber:
|
|
case BTGreaterStrategyNumber:
|
|
addflags = SK_BT_REQBKWD;
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized StrategyNumber: %d",
|
|
(int) skey->sk_strategy);
|
|
addflags = 0; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
|
|
skey->sk_flags |= addflags;
|
|
|
|
if (skey->sk_flags & SK_ROW_HEADER)
|
|
{
|
|
ScanKey subkey = (ScanKey) DatumGetPointer(skey->sk_argument);
|
|
AttrNumber attno = skey->sk_attno;
|
|
|
|
/* First subkey should be same column/operator as the header */
|
|
Assert(subkey->sk_attno == attno);
|
|
Assert(subkey->sk_strategy == skey->sk_strategy);
|
|
|
|
for (;;)
|
|
{
|
|
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
|
if (subkey->sk_attno != attno)
|
|
break; /* non-adjacent key, so not required */
|
|
if (subkey->sk_strategy != skey->sk_strategy)
|
|
break; /* wrong direction, so not required */
|
|
subkey->sk_flags |= addflags;
|
|
if (subkey->sk_flags & SK_ROW_END)
|
|
break;
|
|
subkey++;
|
|
attno++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compare two scankey values using a specified operator.
|
|
*
|
|
* The test we want to perform is logically "leftarg op rightarg", where
|
|
* leftarg and rightarg are the sk_argument values in those ScanKeys, and
|
|
* the comparison operator is the one in the op ScanKey. However, in
|
|
* cross-data-type situations we may need to look up the correct operator in
|
|
* the index's opfamily: it is the one having amopstrategy = op->sk_strategy
|
|
* and amoplefttype/amoprighttype equal to the two argument datatypes.
|
|
*
|
|
* If the opfamily doesn't supply a complete set of cross-type operators we
|
|
* may not be able to make the comparison. If we can make the comparison
|
|
* we store the operator result in *result and return true. We return false
|
|
* if the comparison could not be made.
|
|
*
|
|
* If either leftarg or rightarg are an array, we'll apply array-specific
|
|
* rules to determine which array elements are redundant on behalf of caller.
|
|
* It is up to our caller to save whichever of the two scan keys is the array,
|
|
* and discard the non-array scan key (the non-array scan key is guaranteed to
|
|
* be redundant with any complete opfamily). Caller isn't expected to call
|
|
* here with a pair of array scan keys provided we're dealing with a complete
|
|
* opfamily (_bt_preprocess_array_keys will merge array keys together to make
|
|
* sure of that).
|
|
*
|
|
* Note: we'll also shrink caller's array as needed to eliminate redundant
|
|
* array elements. One reason why caller should prefer to discard non-array
|
|
* scan keys is so that we'll have the opportunity to shrink the array
|
|
* multiple times, in multiple calls (for each of several other scan keys on
|
|
* the same index attribute).
|
|
*
|
|
* Note: op always points at the same ScanKey as either leftarg or rightarg.
|
|
* Since we don't scribble on the scankeys themselves, this aliasing should
|
|
* cause no trouble.
|
|
*
|
|
* Note: this routine needs to be insensitive to any DESC option applied
|
|
* to the index column. For example, "x < 4" is a tighter constraint than
|
|
* "x < 5" regardless of which way the index is sorted.
|
|
*/
|
|
static bool
|
|
_bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
|
|
ScanKey leftarg, ScanKey rightarg,
|
|
BTArrayKeyInfo *array, FmgrInfo *orderproc,
|
|
bool *result)
|
|
{
|
|
Relation rel = scan->indexRelation;
|
|
Oid lefttype,
|
|
righttype,
|
|
optype,
|
|
opcintype,
|
|
cmp_op;
|
|
StrategyNumber strat;
|
|
|
|
Assert(!((leftarg->sk_flags | rightarg->sk_flags) & SK_ROW_MEMBER));
|
|
|
|
/*
|
|
* First, deal with cases where one or both args are NULL. This should
|
|
* only happen when the scankeys represent IS NULL/NOT NULL conditions.
|
|
*/
|
|
if ((leftarg->sk_flags | rightarg->sk_flags) & SK_ISNULL)
|
|
{
|
|
bool leftnull,
|
|
rightnull;
|
|
|
|
/* Handle skip array comparison with IS NOT NULL scan key */
|
|
if ((leftarg->sk_flags | rightarg->sk_flags) & SK_BT_SKIP)
|
|
{
|
|
/* Shouldn't generate skip array in presence of IS NULL key */
|
|
Assert(!((leftarg->sk_flags | rightarg->sk_flags) & SK_SEARCHNULL));
|
|
Assert((leftarg->sk_flags | rightarg->sk_flags) & SK_SEARCHNOTNULL);
|
|
|
|
/* Skip array will have no NULL element/IS NULL scan key */
|
|
Assert(array->num_elems == -1);
|
|
array->null_elem = false;
|
|
|
|
/* IS NOT NULL key (could be leftarg or rightarg) now redundant */
|
|
*result = true;
|
|
return true;
|
|
}
|
|
|
|
if (leftarg->sk_flags & SK_ISNULL)
|
|
{
|
|
Assert(leftarg->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL));
|
|
leftnull = true;
|
|
}
|
|
else
|
|
leftnull = false;
|
|
if (rightarg->sk_flags & SK_ISNULL)
|
|
{
|
|
Assert(rightarg->sk_flags & (SK_SEARCHNULL | SK_SEARCHNOTNULL));
|
|
rightnull = true;
|
|
}
|
|
else
|
|
rightnull = false;
|
|
|
|
/*
|
|
* We treat NULL as either greater than or less than all other values.
|
|
* Since true > false, the tests below work correctly for NULLS LAST
|
|
* logic. If the index is NULLS FIRST, we need to flip the strategy.
|
|
*/
|
|
strat = op->sk_strategy;
|
|
if (op->sk_flags & SK_BT_NULLS_FIRST)
|
|
strat = BTCommuteStrategyNumber(strat);
|
|
|
|
switch (strat)
|
|
{
|
|
case BTLessStrategyNumber:
|
|
*result = (leftnull < rightnull);
|
|
break;
|
|
case BTLessEqualStrategyNumber:
|
|
*result = (leftnull <= rightnull);
|
|
break;
|
|
case BTEqualStrategyNumber:
|
|
*result = (leftnull == rightnull);
|
|
break;
|
|
case BTGreaterEqualStrategyNumber:
|
|
*result = (leftnull >= rightnull);
|
|
break;
|
|
case BTGreaterStrategyNumber:
|
|
*result = (leftnull > rightnull);
|
|
break;
|
|
default:
|
|
elog(ERROR, "unrecognized StrategyNumber: %d", (int) strat);
|
|
*result = false; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* We don't yet know how to determine redundancy when it involves a row
|
|
* compare key (barring simple cases involving IS NULL/IS NOT NULL)
|
|
*/
|
|
if ((leftarg->sk_flags | rightarg->sk_flags) & SK_ROW_HEADER)
|
|
{
|
|
Assert(!((leftarg->sk_flags | rightarg->sk_flags) & SK_BT_SKIP));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If either leftarg or rightarg are equality-type array scankeys, we need
|
|
* specialized handling (since by now we know that IS NULL wasn't used)
|
|
*/
|
|
if (array)
|
|
{
|
|
bool leftarray,
|
|
rightarray;
|
|
|
|
leftarray = ((leftarg->sk_flags & SK_SEARCHARRAY) &&
|
|
leftarg->sk_strategy == BTEqualStrategyNumber);
|
|
rightarray = ((rightarg->sk_flags & SK_SEARCHARRAY) &&
|
|
rightarg->sk_strategy == BTEqualStrategyNumber);
|
|
|
|
/*
|
|
* _bt_preprocess_array_keys is responsible for merging together array
|
|
* scan keys, and will do so whenever the opfamily has the required
|
|
* cross-type support. If it failed to do that, we handle it just
|
|
* like the case where we can't make the comparison ourselves.
|
|
*/
|
|
if (leftarray && rightarray)
|
|
{
|
|
/* Can't make the comparison */
|
|
*result = false; /* suppress compiler warnings */
|
|
Assert(!((leftarg->sk_flags | rightarg->sk_flags) & SK_BT_SKIP));
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Otherwise we need to determine if either one of leftarg or rightarg
|
|
* uses an array, then pass this through to a dedicated helper
|
|
* function.
|
|
*/
|
|
if (leftarray)
|
|
return _bt_compare_array_scankey_args(scan, leftarg, rightarg,
|
|
orderproc, array, result);
|
|
else if (rightarray)
|
|
return _bt_compare_array_scankey_args(scan, rightarg, leftarg,
|
|
orderproc, array, result);
|
|
|
|
/* FALL THRU */
|
|
}
|
|
|
|
/*
|
|
* The opfamily we need to worry about is identified by the index column.
|
|
*/
|
|
Assert(leftarg->sk_attno == rightarg->sk_attno);
|
|
|
|
opcintype = rel->rd_opcintype[leftarg->sk_attno - 1];
|
|
|
|
/*
|
|
* Determine the actual datatypes of the ScanKey arguments. We have to
|
|
* support the convention that sk_subtype == InvalidOid means the opclass
|
|
* input type; this is a hack to simplify life for ScanKeyInit().
|
|
*/
|
|
lefttype = leftarg->sk_subtype;
|
|
if (lefttype == InvalidOid)
|
|
lefttype = opcintype;
|
|
righttype = rightarg->sk_subtype;
|
|
if (righttype == InvalidOid)
|
|
righttype = opcintype;
|
|
optype = op->sk_subtype;
|
|
if (optype == InvalidOid)
|
|
optype = opcintype;
|
|
|
|
/*
|
|
* If leftarg and rightarg match the types expected for the "op" scankey,
|
|
* we can use its already-looked-up comparison function.
|
|
*/
|
|
if (lefttype == opcintype && righttype == optype)
|
|
{
|
|
*result = DatumGetBool(FunctionCall2Coll(&op->sk_func,
|
|
op->sk_collation,
|
|
leftarg->sk_argument,
|
|
rightarg->sk_argument));
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Otherwise, we need to go to the syscache to find the appropriate
|
|
* operator. (This cannot result in infinite recursion, since no
|
|
* indexscan initiated by syscache lookup will use cross-data-type
|
|
* operators.)
|
|
*
|
|
* If the sk_strategy was flipped by _bt_fix_scankey_strategy, we have to
|
|
* un-flip it to get the correct opfamily member.
|
|
*/
|
|
strat = op->sk_strategy;
|
|
if (op->sk_flags & SK_BT_DESC)
|
|
strat = BTCommuteStrategyNumber(strat);
|
|
|
|
cmp_op = get_opfamily_member(rel->rd_opfamily[leftarg->sk_attno - 1],
|
|
lefttype,
|
|
righttype,
|
|
strat);
|
|
if (OidIsValid(cmp_op))
|
|
{
|
|
RegProcedure cmp_proc = get_opcode(cmp_op);
|
|
|
|
if (RegProcedureIsValid(cmp_proc))
|
|
{
|
|
*result = DatumGetBool(OidFunctionCall2Coll(cmp_proc,
|
|
op->sk_collation,
|
|
leftarg->sk_argument,
|
|
rightarg->sk_argument));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Can't make the comparison */
|
|
*result = false; /* suppress compiler warnings */
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Compare an array scan key to a scalar scan key, eliminating contradictory
|
|
* array elements such that the scalar scan key becomes redundant.
|
|
*
|
|
* If the opfamily is incomplete we may not be able to determine which
|
|
* elements are contradictory. When we return true we'll have validly set
|
|
* *qual_ok, guaranteeing that at least the scalar scan key can be considered
|
|
* redundant. We return false if the comparison could not be made (caller
|
|
* must keep both scan keys when this happens).
|
|
*
|
|
* Note: it's up to caller to deal with IS [NOT] NULL scan keys, as well as
|
|
* row comparison scan keys. We only deal with scalar scan keys.
|
|
*/
|
|
static bool
|
|
_bt_compare_array_scankey_args(IndexScanDesc scan, ScanKey arraysk, ScanKey skey,
|
|
FmgrInfo *orderproc, BTArrayKeyInfo *array,
|
|
bool *qual_ok)
|
|
{
|
|
Assert(arraysk->sk_attno == skey->sk_attno);
|
|
Assert(!(arraysk->sk_flags & (SK_ISNULL | SK_ROW_HEADER | SK_ROW_MEMBER)));
|
|
Assert((arraysk->sk_flags & SK_SEARCHARRAY) &&
|
|
arraysk->sk_strategy == BTEqualStrategyNumber);
|
|
/* don't expect to have to deal with NULLs/row comparison scan keys */
|
|
Assert(!(skey->sk_flags & (SK_ISNULL | SK_ROW_HEADER | SK_ROW_MEMBER)));
|
|
Assert(!(skey->sk_flags & SK_SEARCHARRAY) ||
|
|
skey->sk_strategy != BTEqualStrategyNumber);
|
|
|
|
/*
|
|
* Just call the appropriate helper function based on whether it's a SAOP
|
|
* array or a skip array. Both helpers will set *qual_ok in passing.
|
|
*/
|
|
if (array->num_elems != -1)
|
|
return _bt_saoparray_shrink(scan, arraysk, skey, orderproc, array,
|
|
qual_ok);
|
|
else
|
|
return _bt_skiparray_shrink(scan, skey, array, qual_ok);
|
|
}
|
|
|
|
/*
|
|
* Preprocessing of SAOP array scan key, used to determine which array
|
|
* elements are eliminated as contradictory by a non-array scalar key.
|
|
*
|
|
* _bt_compare_array_scankey_args helper function.
|
|
*
|
|
* Array elements can be eliminated as contradictory when excluded by some
|
|
* other operator on the same attribute. For example, with an index scan qual
|
|
* "WHERE a IN (1, 2, 3) AND a < 2", all array elements except the value "1"
|
|
* are eliminated, and the < scan key is eliminated as redundant. Cases where
|
|
* every array element is eliminated by a redundant scalar scan key have an
|
|
* unsatisfiable qual, which we handle by setting *qual_ok=false for caller.
|
|
*/
|
|
static bool
|
|
_bt_saoparray_shrink(IndexScanDesc scan, ScanKey arraysk, ScanKey skey,
|
|
FmgrInfo *orderproc, BTArrayKeyInfo *array, bool *qual_ok)
|
|
{
|
|
Relation rel = scan->indexRelation;
|
|
Oid opcintype = rel->rd_opcintype[arraysk->sk_attno - 1];
|
|
int cmpresult = 0,
|
|
cmpexact = 0,
|
|
matchelem,
|
|
new_nelems = 0;
|
|
FmgrInfo crosstypeproc;
|
|
FmgrInfo *orderprocp = orderproc;
|
|
|
|
Assert(array->num_elems > 0);
|
|
Assert(!(arraysk->sk_flags & SK_BT_SKIP));
|
|
|
|
/*
|
|
* _bt_binsrch_array_skey searches an array for the entry best matching a
|
|
* datum of opclass input type for the index's attribute (on-disk type).
|
|
* We can reuse the array's ORDER proc whenever the non-array scan key's
|
|
* type is a match for the corresponding attribute's input opclass type.
|
|
* Otherwise, we have to do another ORDER proc lookup so that our call to
|
|
* _bt_binsrch_array_skey applies the correct comparator.
|
|
*
|
|
* Note: we have to support the convention that sk_subtype == InvalidOid
|
|
* means the opclass input type; this is a hack to simplify life for
|
|
* ScanKeyInit().
|
|
*/
|
|
if (skey->sk_subtype != opcintype && skey->sk_subtype != InvalidOid)
|
|
{
|
|
RegProcedure cmp_proc;
|
|
Oid arraysk_elemtype;
|
|
|
|
/*
|
|
* Need an ORDER proc lookup to detect redundancy/contradictoriness
|
|
* with this pair of scankeys.
|
|
*
|
|
* Scalar scan key's argument will be passed to _bt_compare_array_skey
|
|
* as its tupdatum/lefthand argument (rhs arg is for array elements).
|
|
*/
|
|
arraysk_elemtype = arraysk->sk_subtype;
|
|
if (arraysk_elemtype == InvalidOid)
|
|
arraysk_elemtype = rel->rd_opcintype[arraysk->sk_attno - 1];
|
|
cmp_proc = get_opfamily_proc(rel->rd_opfamily[arraysk->sk_attno - 1],
|
|
skey->sk_subtype, arraysk_elemtype,
|
|
BTORDER_PROC);
|
|
if (!RegProcedureIsValid(cmp_proc))
|
|
{
|
|
/* Can't make the comparison */
|
|
*qual_ok = false; /* suppress compiler warnings */
|
|
return false;
|
|
}
|
|
|
|
/* We have all we need to determine redundancy/contradictoriness */
|
|
orderprocp = &crosstypeproc;
|
|
fmgr_info(cmp_proc, orderprocp);
|
|
}
|
|
|
|
matchelem = _bt_binsrch_array_skey(orderprocp, false,
|
|
NoMovementScanDirection,
|
|
skey->sk_argument, false, array,
|
|
arraysk, &cmpresult);
|
|
|
|
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;
|
|
}
|
|
|
|
Assert(new_nelems >= 0);
|
|
Assert(new_nelems <= array->num_elems);
|
|
|
|
array->num_elems = new_nelems;
|
|
*qual_ok = new_nelems > 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Preprocessing of skip array scan key, used to determine redundancy against
|
|
* a non-array scalar scan key (must be an inequality).
|
|
*
|
|
* _bt_compare_array_scankey_args helper function.
|
|
*
|
|
* Skip arrays work by procedurally generating their elements as needed, so we
|
|
* just store the inequality as the skip array's low_compare or high_compare
|
|
* (except when there's already a more restrictive low_compare/high_compare).
|
|
* The array's final elements are the range of values that still satisfy the
|
|
* array's final low_compare and high_compare.
|
|
*/
|
|
static bool
|
|
_bt_skiparray_shrink(IndexScanDesc scan, ScanKey skey, BTArrayKeyInfo *array,
|
|
bool *qual_ok)
|
|
{
|
|
bool test_result;
|
|
|
|
Assert(array->num_elems == -1);
|
|
|
|
/*
|
|
* Array's index attribute will be constrained by a strict operator/key.
|
|
* Array must not "contain a NULL element" (i.e. the scan must not apply
|
|
* "IS NULL" qual when it reaches the end of the index that stores NULLs).
|
|
*/
|
|
array->null_elem = false;
|
|
*qual_ok = true;
|
|
|
|
/*
|
|
* Consider if we should treat caller's scalar scan key as the skip
|
|
* array's high_compare or low_compare.
|
|
*
|
|
* In general the current array element must either be a copy of a value
|
|
* taken from an index tuple, or a derivative value generated by opclass's
|
|
* skip support function. That way the scan can always safely assume that
|
|
* it's okay to use the only-input-opclass-type proc from so->orderProcs[]
|
|
* (they can be cross-type with SAOP arrays, but never with skip arrays).
|
|
*
|
|
* This approach is enabled by MINVAL/MAXVAL sentinel key markings, which
|
|
* can be thought of as representing either the lowest or highest matching
|
|
* array element (excluding the NULL element, where applicable, though as
|
|
* just discussed it isn't applicable to this range skip array anyway).
|
|
* Array keys marked MINVAL/MAXVAL never have a valid datum in their
|
|
* sk_argument field. The scan directly applies the array's low_compare
|
|
* key when it encounters MINVAL in the array key proper (just as it
|
|
* applies high_compare when it sees MAXVAL set in the array key proper).
|
|
* The scan must never use the array's so->orderProcs[] proc against
|
|
* low_compare's/high_compare's sk_argument, either (so->orderProcs[] is
|
|
* only intended to be used with rhs datums from the array proper/index).
|
|
*/
|
|
switch (skey->sk_strategy)
|
|
{
|
|
case BTLessStrategyNumber:
|
|
case BTLessEqualStrategyNumber:
|
|
if (array->high_compare)
|
|
{
|
|
/* replace existing high_compare with caller's key? */
|
|
if (!_bt_compare_scankey_args(scan, array->high_compare, skey,
|
|
array->high_compare, NULL, NULL,
|
|
&test_result))
|
|
return false; /* can't determine more restrictive key */
|
|
|
|
if (!test_result)
|
|
return true; /* no, just discard caller's key */
|
|
|
|
/* yes, replace existing high_compare with caller's key */
|
|
}
|
|
|
|
/* caller's key becomes skip array's high_compare */
|
|
array->high_compare = skey;
|
|
break;
|
|
case BTGreaterEqualStrategyNumber:
|
|
case BTGreaterStrategyNumber:
|
|
if (array->low_compare)
|
|
{
|
|
/* replace existing low_compare with caller's key? */
|
|
if (!_bt_compare_scankey_args(scan, array->low_compare, skey,
|
|
array->low_compare, NULL, NULL,
|
|
&test_result))
|
|
return false; /* can't determine more restrictive key */
|
|
|
|
if (!test_result)
|
|
return true; /* no, just discard caller's key */
|
|
|
|
/* yes, replace existing low_compare with caller's key */
|
|
}
|
|
|
|
/* caller's key becomes skip array's low_compare */
|
|
array->low_compare = skey;
|
|
break;
|
|
case BTEqualStrategyNumber:
|
|
default:
|
|
elog(ERROR, "unrecognized StrategyNumber: %d",
|
|
(int) skey->sk_strategy);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Applies the opfamily's skip support routine to convert the skip array's >
|
|
* low_compare key (if any) into a >= key, and to convert its < high_compare
|
|
* key (if any) into a <= key. Decrements the high_compare key's sk_argument,
|
|
* and/or increments the low_compare key's sk_argument (also adjusts their
|
|
* operator strategies, while changing the operator as appropriate).
|
|
*
|
|
* This optional optimization reduces the number of descents required within
|
|
* _bt_first. Whenever _bt_first is called with a skip array whose current
|
|
* array element is the sentinel value MINVAL, using a transformed >= key
|
|
* instead of using the original > key makes it safe to include lower-order
|
|
* scan keys in the insertion scan key (there must be lower-order scan keys
|
|
* after the skip array). We will avoid an extra _bt_first to find the first
|
|
* value in the index > sk_argument -- at least when the first real matching
|
|
* value in the index happens to be an exact match for the sk_argument value
|
|
* that we produced here by incrementing the original input key's sk_argument.
|
|
* (Backwards scans derive the same benefit when they encounter the sentinel
|
|
* value MAXVAL, by converting the high_compare key from < to <=.)
|
|
*
|
|
* Note: The transformation is only correct when it cannot allow the scan to
|
|
* overlook matching tuples, but we don't have enough semantic information to
|
|
* safely make sure that can't happen during scans with cross-type operators.
|
|
* That's why we'll never apply the transformation in cross-type scenarios.
|
|
* For example, if we attempted to convert "sales_ts > '2024-01-01'::date"
|
|
* into "sales_ts >= '2024-01-02'::date" given a "sales_ts" attribute whose
|
|
* input opclass is timestamp_ops, the scan would overlook almost all (or all)
|
|
* tuples for sales that fell on '2024-01-01'.
|
|
*
|
|
* Note: We can safely modify array->low_compare/array->high_compare in place
|
|
* because they just point to copies of our scan->keyData[] input scan keys
|
|
* (namely the copies returned by _bt_preprocess_array_keys to be used as
|
|
* input into the standard preprocessing steps in _bt_preprocess_keys).
|
|
* Everything will be reset if there's a rescan.
|
|
*/
|
|
static void
|
|
_bt_skiparray_strat_adjust(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array)
|
|
{
|
|
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
|
MemoryContext oldContext;
|
|
|
|
/*
|
|
* Called last among all preprocessing steps, when the skip array's final
|
|
* low_compare and high_compare have both been chosen
|
|
*/
|
|
Assert(arraysk->sk_flags & SK_BT_SKIP);
|
|
Assert(array->num_elems == -1 && !array->null_elem && array->sksup);
|
|
|
|
oldContext = MemoryContextSwitchTo(so->arrayContext);
|
|
|
|
if (array->high_compare &&
|
|
array->high_compare->sk_strategy == BTLessStrategyNumber)
|
|
_bt_skiparray_strat_decrement(scan, arraysk, array);
|
|
|
|
if (array->low_compare &&
|
|
array->low_compare->sk_strategy == BTGreaterStrategyNumber)
|
|
_bt_skiparray_strat_increment(scan, arraysk, array);
|
|
|
|
MemoryContextSwitchTo(oldContext);
|
|
}
|
|
|
|
/*
|
|
* Convert skip array's > low_compare key into a >= key
|
|
*/
|
|
static void
|
|
_bt_skiparray_strat_decrement(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array)
|
|
{
|
|
Relation rel = scan->indexRelation;
|
|
Oid opfamily = rel->rd_opfamily[arraysk->sk_attno - 1],
|
|
opcintype = rel->rd_opcintype[arraysk->sk_attno - 1],
|
|
leop;
|
|
RegProcedure cmp_proc;
|
|
ScanKey high_compare = array->high_compare;
|
|
Datum orig_sk_argument = high_compare->sk_argument,
|
|
new_sk_argument;
|
|
bool uflow;
|
|
int16 lookupstrat;
|
|
|
|
Assert(high_compare->sk_strategy == BTLessStrategyNumber);
|
|
|
|
/*
|
|
* Only perform the transformation when the operator type matches the
|
|
* index attribute's input opclass type
|
|
*/
|
|
if (high_compare->sk_subtype != opcintype &&
|
|
high_compare->sk_subtype != InvalidOid)
|
|
return;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* Look up <= operator (might fail), accounting for the fact that a
|
|
* high_compare on a DESC column already had its strategy commuted
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert skip array's < low_compare key into a <= key
|
|
*/
|
|
static void
|
|
_bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk,
|
|
BTArrayKeyInfo *array)
|
|
{
|
|
Relation rel = scan->indexRelation;
|
|
Oid opfamily = rel->rd_opfamily[arraysk->sk_attno - 1],
|
|
opcintype = rel->rd_opcintype[arraysk->sk_attno - 1],
|
|
geop;
|
|
RegProcedure cmp_proc;
|
|
ScanKey low_compare = array->low_compare;
|
|
Datum orig_sk_argument = low_compare->sk_argument,
|
|
new_sk_argument;
|
|
bool oflow;
|
|
int16 lookupstrat;
|
|
|
|
Assert(low_compare->sk_strategy == BTGreaterStrategyNumber);
|
|
|
|
/*
|
|
* Only perform the transformation when the operator type matches the
|
|
* index attribute's input opclass type
|
|
*/
|
|
if (low_compare->sk_subtype != opcintype &&
|
|
low_compare->sk_subtype != InvalidOid)
|
|
return;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/*
|
|
* Look up >= operator (might fail), accounting for the fact that a
|
|
* low_compare on a DESC column already had its strategy commuted
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* _bt_unmark_keys() -- make superfluous required keys nonrequired after all
|
|
*
|
|
* When _bt_preprocess_keys fails to eliminate one or more redundant keys, it
|
|
* calls here to make sure that no index attribute has more than one > or >=
|
|
* key marked required, and no more than one required < or <= key. Attributes
|
|
* with = keys will always get one = key as their required key. All other
|
|
* keys that were initially marked required get "unmarked" here. That way,
|
|
* _bt_first and _bt_checkkeys will reliably agree on which keys to use to
|
|
* start and/or to end the scan.
|
|
*
|
|
* We also relocate keys that become/started out nonrequired to the end of
|
|
* so->keyData[]. That way, _bt_first and _bt_checkkeys cannot fail to reach
|
|
* a required key due to some earlier nonrequired key getting in the way.
|
|
*
|
|
* Only call here when _bt_compare_scankey_args returned false at least once
|
|
* (otherwise, calling here will just waste cycles).
|
|
*/
|
|
static void
|
|
_bt_unmark_keys(IndexScanDesc scan, int *keyDataMap)
|
|
{
|
|
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
|
AttrNumber attno;
|
|
bool *unmarkikey;
|
|
int nunmark,
|
|
nunmarked,
|
|
nkept,
|
|
firsti;
|
|
ScanKey keepKeys,
|
|
unmarkKeys;
|
|
FmgrInfo *keepOrderProcs = NULL,
|
|
*unmarkOrderProcs = NULL;
|
|
bool haveReqEquals,
|
|
haveReqForward,
|
|
haveReqBackward;
|
|
|
|
/*
|
|
* Do an initial pass over so->keyData[] that determines which keys to
|
|
* keep as required. We expect so->keyData[] to still be in attribute
|
|
* order when we're called (though we don't expect any particular order
|
|
* among each attribute's keys).
|
|
*
|
|
* When both equality and inequality keys remain on a single attribute, we
|
|
* *must* make sure that exactly one of the equalities remains required.
|
|
* Any requiredness markings that we might leave on later keys/attributes
|
|
* are predicated on there being required = keys on all prior columns.
|
|
*/
|
|
unmarkikey = palloc0(so->numberOfKeys * sizeof(bool));
|
|
nunmark = 0;
|
|
|
|
/* 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;
|
|
|
|
haveReqEquals = false;
|
|
haveReqForward = false;
|
|
haveReqBackward = false;
|
|
}
|
|
|
|
/* Equalities get priority over inequalities */
|
|
if (haveReqEquals)
|
|
{
|
|
/*
|
|
* We already found the first "=" key for this attribute. We've
|
|
* already decided that all its other keys will be unmarked.
|
|
*/
|
|
Assert(!(origkey->sk_flags & SK_SEARCHNULL));
|
|
unmarkikey[i] = true;
|
|
nunmark++;
|
|
continue;
|
|
}
|
|
else if ((origkey->sk_flags & SK_BT_REQFWD) &&
|
|
(origkey->sk_flags & SK_BT_REQBKWD))
|
|
{
|
|
/*
|
|
* Found the first "=" key for attno. All other attno keys will
|
|
* be unmarked.
|
|
*/
|
|
Assert(origkey->sk_strategy == BTEqualStrategyNumber);
|
|
|
|
haveReqEquals = true;
|
|
for (int j = firsti; j < i; j++)
|
|
{
|
|
/* Unmark any prior inequality keys on attno after all */
|
|
if (!unmarkikey[j])
|
|
{
|
|
unmarkikey[j] = true;
|
|
nunmark++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Deal with inequalities next */
|
|
if ((origkey->sk_flags & SK_BT_REQFWD) && !haveReqForward)
|
|
{
|
|
haveReqForward = true;
|
|
continue;
|
|
}
|
|
else if ((origkey->sk_flags & SK_BT_REQBKWD) && !haveReqBackward)
|
|
{
|
|
haveReqBackward = true;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We have either a redundant inequality key that will be unmarked, or
|
|
* we have a key that wasn't marked required in the first place
|
|
*/
|
|
unmarkikey[i] = true;
|
|
nunmark++;
|
|
}
|
|
|
|
/* Should only be called when _bt_compare_scankey_args reported failure */
|
|
Assert(nunmark > 0);
|
|
|
|
/*
|
|
* Next, allocate temp arrays: one for required keys that'll remain
|
|
* required, the other for all remaining keys
|
|
*/
|
|
unmarkKeys = palloc(nunmark * sizeof(ScanKeyData));
|
|
keepKeys = palloc((so->numberOfKeys - nunmark) * sizeof(ScanKeyData));
|
|
nunmarked = 0;
|
|
nkept = 0;
|
|
if (so->numArrayKeys)
|
|
{
|
|
unmarkOrderProcs = palloc(nunmark * sizeof(FmgrInfo));
|
|
keepOrderProcs = palloc((so->numberOfKeys - nunmark) * sizeof(FmgrInfo));
|
|
}
|
|
|
|
/*
|
|
* Next, copy the contents of so->keyData[] into the appropriate temp
|
|
* array.
|
|
*
|
|
* Scans with = array keys need us to maintain invariants around the order
|
|
* of so->orderProcs[] and so->arrayKeys[] relative to so->keyData[]. See
|
|
* _bt_preprocess_array_keys_final for a full explanation.
|
|
*/
|
|
for (int i = 0; i < so->numberOfKeys; i++)
|
|
{
|
|
ScanKey origkey = &so->keyData[i];
|
|
ScanKey unmark;
|
|
|
|
if (!unmarkikey[i])
|
|
{
|
|
/*
|
|
* Key gets to keep its original requiredness markings.
|
|
*
|
|
* Key will stay in its original position, unless we're going to
|
|
* unmark an earlier key (in which case this key gets moved back).
|
|
*/
|
|
memcpy(keepKeys + nkept, origkey, sizeof(ScanKeyData));
|
|
|
|
if (so->numArrayKeys)
|
|
{
|
|
keyDataMap[i] = nkept;
|
|
memcpy(keepOrderProcs + nkept, &so->orderProcs[i],
|
|
sizeof(FmgrInfo));
|
|
}
|
|
|
|
nkept++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Key will be unmarked as needed, and moved to the end of the array,
|
|
* next to other keys that will become (or always were) nonrequired
|
|
*/
|
|
unmark = unmarkKeys + nunmarked;
|
|
memcpy(unmark, origkey, sizeof(ScanKeyData));
|
|
|
|
if (so->numArrayKeys)
|
|
{
|
|
keyDataMap[i] = (so->numberOfKeys - nunmark) + nunmarked;
|
|
memcpy(&unmarkOrderProcs[nunmarked], &so->orderProcs[i],
|
|
sizeof(FmgrInfo));
|
|
}
|
|
|
|
/*
|
|
* Preprocessing only generates skip arrays when it knows that they'll
|
|
* be the only required = key on the attr. We'll never unmark them.
|
|
*/
|
|
Assert(!(unmark->sk_flags & SK_BT_SKIP));
|
|
|
|
/*
|
|
* Also shouldn't have to unmark an IS NULL or an IS NOT NULL key.
|
|
* They aren't cross-type, so an incomplete opfamily can't matter.
|
|
*/
|
|
Assert(!(unmark->sk_flags & SK_ISNULL) ||
|
|
!(unmark->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)));
|
|
|
|
/* Clear requiredness flags on redundant key (and on any subkeys) */
|
|
unmark->sk_flags &= ~(SK_BT_REQFWD | SK_BT_REQBKWD);
|
|
if (unmark->sk_flags & SK_ROW_HEADER)
|
|
{
|
|
ScanKey subkey = (ScanKey) DatumGetPointer(unmark->sk_argument);
|
|
|
|
Assert(subkey->sk_strategy == unmark->sk_strategy);
|
|
for (;;)
|
|
{
|
|
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
|
subkey->sk_flags &= ~(SK_BT_REQFWD | SK_BT_REQBKWD);
|
|
if (subkey->sk_flags & SK_ROW_END)
|
|
break;
|
|
subkey++;
|
|
}
|
|
}
|
|
|
|
nunmarked++;
|
|
}
|
|
|
|
/* Copy both temp arrays back into so->keyData[] to reorder */
|
|
Assert(nkept == so->numberOfKeys - nunmark);
|
|
Assert(nunmarked == nunmark);
|
|
memcpy(so->keyData, keepKeys, sizeof(ScanKeyData) * nkept);
|
|
memcpy(so->keyData + nkept, unmarkKeys, sizeof(ScanKeyData) * nunmarked);
|
|
|
|
/* Done with temp arrays */
|
|
pfree(unmarkikey);
|
|
pfree(keepKeys);
|
|
pfree(unmarkKeys);
|
|
|
|
/*
|
|
* Now copy so->orderProcs[] temp entries needed by scans with = array
|
|
* keys back (just like with the so->keyData[] temp arrays)
|
|
*/
|
|
if (so->numArrayKeys)
|
|
{
|
|
memcpy(so->orderProcs, keepOrderProcs, sizeof(FmgrInfo) * nkept);
|
|
memcpy(so->orderProcs + nkept, unmarkOrderProcs,
|
|
sizeof(FmgrInfo) * nunmarked);
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* qsort comparator for reordering so->arrayKeys[] BTArrayKeyInfo entries
|
|
*/
|
|
static int
|
|
_bt_reorder_array_cmp(const void *a, const void *b)
|
|
{
|
|
BTArrayKeyInfo *arraya = (BTArrayKeyInfo *) a;
|
|
BTArrayKeyInfo *arrayb = (BTArrayKeyInfo *) b;
|
|
|
|
return pg_cmp_s32(arraya->scan_key, arrayb->scan_key);
|
|
}
|
|
|
|
/*
|
|
* _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);
|
|
|
|
oldContext = MemoryContextSwitchTo(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);
|
|
|
|
ScanKeyEntryInitialize(cur,
|
|
SK_SEARCHARRAY | SK_BT_SKIP, /* flags */
|
|
attno_skip, /* skipped att number */
|
|
BTEqualStrategyNumber, /* equality strategy */
|
|
InvalidOid, /* opclass input subtype */
|
|
collation, /* index column's collation */
|
|
cmp_proc, /* equality operator's proc */
|
|
(Datum) 0); /* constant */
|
|
|
|
/* Initialize generic BTArrayKeyInfo fields */
|
|
so->arrayKeys[numArrayKeys].scan_key = numArrayKeyData;
|
|
so->arrayKeys[numArrayKeys].num_elems = -1;
|
|
|
|
/* Initialize skip array specific BTArrayKeyInfo fields */
|
|
attr = TupleDescCompactAttr(RelationGetDescr(rel), attno_skip - 1);
|
|
reverse = (indoption[attno_skip - 1] & INDOPTION_DESC) != 0;
|
|
so->arrayKeys[numArrayKeys].attlen = attr->attlen;
|
|
so->arrayKeys[numArrayKeys].attbyval = attr->attbyval;
|
|
so->arrayKeys[numArrayKeys].null_elem = true; /* for now */
|
|
so->arrayKeys[numArrayKeys].sksup =
|
|
PrepareSkipSupportFromOpclass(opfamily, opcintype, reverse);
|
|
so->arrayKeys[numArrayKeys].low_compare = NULL; /* for now */
|
|
so->arrayKeys[numArrayKeys].high_compare = NULL; /* for now */
|
|
|
|
/*
|
|
* We'll need a 3-way ORDER proc. Set that up now.
|
|
*/
|
|
_bt_setup_array_cmp(scan, cur, opcintype,
|
|
&so->orderProcs[numArrayKeyData], NULL);
|
|
|
|
numArrayKeys++;
|
|
numArrayKeyData++; /* keep this scan key/array */
|
|
|
|
/* set up next output scan key */
|
|
cur = &arrayKeyData[numArrayKeyData];
|
|
|
|
/* remember having output this skip array and scan key */
|
|
numSkipArrayKeys--;
|
|
attno_skip++;
|
|
}
|
|
|
|
/*
|
|
* Provisionally copy scan key into arrayKeyData[] array we'll return
|
|
* to _bt_preprocess_keys caller
|
|
*/
|
|
*cur = *inkey;
|
|
|
|
if (!(cur->sk_flags & SK_SEARCHARRAY))
|
|
{
|
|
numArrayKeyData++; /* keep this non-array scan key */
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Process SAOP array scan key
|
|
*/
|
|
Assert(!(cur->sk_flags & (SK_ROW_HEADER | SK_SEARCHNULL | SK_SEARCHNOTNULL)));
|
|
|
|
/* 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 generic BTArrayKeyInfo fields */
|
|
so->arrayKeys[numArrayKeys].scan_key = numArrayKeyData;
|
|
so->arrayKeys[numArrayKeys].num_elems = num_elems;
|
|
|
|
/* 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;
|
|
|
|
input_ikey = keyDataMap[output_ikey];
|
|
|
|
Assert(last_equal_output_ikey < output_ikey);
|
|
Assert(last_equal_output_ikey < input_ikey);
|
|
last_equal_output_ikey = output_ikey;
|
|
|
|
/*
|
|
* 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;
|
|
|
|
elemtype = outkey->sk_subtype;
|
|
if (elemtype == InvalidOid)
|
|
elemtype = rel->rd_opcintype[outkey->sk_attno - 1];
|
|
|
|
_bt_setup_array_cmp(scan, outkey, elemtype,
|
|
&so->orderProcs[output_ikey], NULL);
|
|
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;
|
|
|
|
/* Shift other arrays forward */
|
|
memmove(array, array + 1,
|
|
sizeof(BTArrayKeyInfo) *
|
|
(so->numArrayKeys - arrayidx));
|
|
|
|
/*
|
|
* 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 = 1 a = 1 (no skip arrays)
|
|
* b = 42 skip a AND b = 42
|
|
* a = 1 AND b = 42 a = 1 AND b = 42 (no skip arrays)
|
|
* a >= 1 AND b = 42 range skip a AND b = 42
|
|
* a = 1 AND b > 42 a = 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 <= 27 a = 1 AND skip b AND c <= 27
|
|
* a = 1 AND d >= 1 a = 1 AND skip b AND skip c AND d >= 1
|
|
* a = 1 AND b >= 42 AND d > 1 a = 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++;
|
|
}
|
|
|
|
#ifdef DEBUG_DISABLE_SKIP_SCAN
|
|
/* don't attempt to add skip arrays */
|
|
return numSAOPArrayKeys;
|
|
#endif
|
|
|
|
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];
|
|
|
|
/* 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]))
|
|
{
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
*numSkipArrayKeys_out = numSkipArrayKeys;
|
|
return numSAOPArrayKeys + numSkipArrayKeys;
|
|
}
|
|
|
|
/*
|
|
* _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];
|
|
|
|
Assert(skey->sk_strategy == BTEqualStrategyNumber);
|
|
Assert(OidIsValid(elemtype));
|
|
|
|
/*
|
|
* 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;
|
|
|
|
Assert(skey->sk_strategy == BTEqualStrategyNumber);
|
|
Assert(OidIsValid(origelemtype) && OidIsValid(nextelemtype));
|
|
|
|
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);
|
|
}
|
|
|
|
cxt.sortproc = mergeproc;
|
|
cxt.collation = skey->sk_collation;
|
|
cxt.reverse = reverse;
|
|
|
|
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;
|
|
}
|