mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 00:02:01 -04:00
Compare commits
No commits in common. "5d0800000ed5e4fb5ed010bb4b93f966e08b9fb3" and "b897a58556d8c29366ae980d65bf5e90daf7098e" have entirely different histories.
5d0800000e
...
b897a58556
@ -37,12 +37,12 @@
|
||||
|
||||
<para>
|
||||
This module provides the <function>pg_buffercache_pages()</function>
|
||||
function (wrapped in the <structname>pg_buffercache</structname> view), the
|
||||
function (wrapped in the <structname>pg_buffercache</structname> view),
|
||||
<function>pg_buffercache_numa_pages()</function> function (wrapped in the
|
||||
<structname>pg_buffercache_numa</structname> view), the
|
||||
<function>pg_buffercache_summary()</function> function, the
|
||||
<function>pg_buffercache_usage_counts()</function> function, the
|
||||
<function>pg_buffercache_evict()</function> function, the
|
||||
<function>pg_buffercache_evict()</function>, the
|
||||
<function>pg_buffercache_evict_relation()</function> function and the
|
||||
<function>pg_buffercache_evict_all()</function> function.
|
||||
</para>
|
||||
@ -55,7 +55,7 @@
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <function>pg_buffercache_numa_pages()</function> function provides
|
||||
The <function>pg_buffercache_numa_pages()</function> provides
|
||||
<acronym>NUMA</acronym> node mappings for shared buffer entries. This
|
||||
information is not part of <function>pg_buffercache_pages()</function>
|
||||
itself, as it is much slower to retrieve.
|
||||
|
@ -431,7 +431,7 @@ static void find_next_unskippable_block(LVRelState *vacrel, bool *skipsallvis);
|
||||
static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf,
|
||||
BlockNumber blkno, Page page,
|
||||
bool sharelock, Buffer vmbuffer);
|
||||
static int lazy_scan_prune(LVRelState *vacrel, Buffer buf,
|
||||
static void lazy_scan_prune(LVRelState *vacrel, Buffer buf,
|
||||
BlockNumber blkno, Page page,
|
||||
Buffer vmbuffer, bool all_visible_according_to_vm,
|
||||
bool *has_lpdead_items, bool *vm_page_frozen);
|
||||
@ -1245,7 +1245,6 @@ lazy_scan_heap(LVRelState *vacrel)
|
||||
Buffer buf;
|
||||
Page page;
|
||||
uint8 blk_info = 0;
|
||||
int ndeleted = 0;
|
||||
bool has_lpdead_items;
|
||||
void *per_buffer_data = NULL;
|
||||
bool vm_page_frozen = false;
|
||||
@ -1388,10 +1387,10 @@ lazy_scan_heap(LVRelState *vacrel)
|
||||
* line pointers previously marked LP_DEAD.
|
||||
*/
|
||||
if (got_cleanup_lock)
|
||||
ndeleted = lazy_scan_prune(vacrel, buf, blkno, page,
|
||||
vmbuffer,
|
||||
blk_info & VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM,
|
||||
&has_lpdead_items, &vm_page_frozen);
|
||||
lazy_scan_prune(vacrel, buf, blkno, page,
|
||||
vmbuffer,
|
||||
blk_info & VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM,
|
||||
&has_lpdead_items, &vm_page_frozen);
|
||||
|
||||
/*
|
||||
* Count an eagerly scanned page as a failure or a success.
|
||||
@ -1482,7 +1481,7 @@ lazy_scan_heap(LVRelState *vacrel)
|
||||
* table has indexes. There will only be newly-freed space if we
|
||||
* held the cleanup lock and lazy_scan_prune() was called.
|
||||
*/
|
||||
if (got_cleanup_lock && vacrel->nindexes == 0 && ndeleted > 0 &&
|
||||
if (got_cleanup_lock && vacrel->nindexes == 0 && has_lpdead_items &&
|
||||
blkno - next_fsm_block_to_vacuum >= VACUUM_FSM_EVERY_PAGES)
|
||||
{
|
||||
FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
|
||||
@ -1937,10 +1936,8 @@ cmpOffsetNumbers(const void *a, const void *b)
|
||||
* *vm_page_frozen is set to true if the page is newly set all-frozen in the
|
||||
* VM. The caller currently only uses this for determining whether an eagerly
|
||||
* scanned page was successfully set all-frozen.
|
||||
*
|
||||
* Returns the number of tuples deleted from the page during HOT pruning.
|
||||
*/
|
||||
static int
|
||||
static void
|
||||
lazy_scan_prune(LVRelState *vacrel,
|
||||
Buffer buf,
|
||||
BlockNumber blkno,
|
||||
@ -2211,8 +2208,6 @@ lazy_scan_prune(LVRelState *vacrel,
|
||||
*vm_page_frozen = true;
|
||||
}
|
||||
}
|
||||
|
||||
return presult.ndeleted;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/nbtree.h"
|
||||
#include "common/int.h"
|
||||
#include "lib/qunique.h"
|
||||
#include "utils/array.h"
|
||||
#include "utils/lsyscache.h"
|
||||
@ -57,8 +56,6 @@ 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,
|
||||
@ -99,7 +96,7 @@ static int _bt_compare_array_elements(const void *a, const void *b, void *arg);
|
||||
* 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
|
||||
* The output keys must be 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.
|
||||
@ -130,36 +127,29 @@ static int _bt_compare_array_elements(const void *a, const void *b, void *arg);
|
||||
* 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
|
||||
* If possible, 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.
|
||||
* we cannot eliminate either. If there are two such keys of the same
|
||||
* operator strategy, the second one is just pushed into the output array
|
||||
* without further processing here. We may also emit both >/>= or both
|
||||
* </<= keys if we can't compare them. The logic about required keys still
|
||||
* works if we don't eliminate redundant keys.
|
||||
*
|
||||
* 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).
|
||||
* Note that one reason we need direction-sensitive required-key flags is
|
||||
* precisely that we may not be able to eliminate redundant keys. Suppose
|
||||
* we have "x > 4::int AND x > 10::bigint", and we are unable to determine
|
||||
* which key is more restrictive for lack of a suitable cross-type operator.
|
||||
* _bt_first will arbitrarily pick one of the keys to do the initial
|
||||
* positioning with. If it picks x > 4, then the x > 10 condition will fail
|
||||
* until we reach index entries > 10; but we can't stop the scan just because
|
||||
* x > 10 is failing. On the other hand, if we are scanning backwards, then
|
||||
* failure of either key is indeed enough to stop the scan. (In general, when
|
||||
* inequality keys are present, the initial-positioning code only promises to
|
||||
* position before the first possible match, not exactly at the first match,
|
||||
* for a forward scan; or after the last match for a backward scan.)
|
||||
*
|
||||
* 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,
|
||||
@ -198,8 +188,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
int numberOfEqualCols;
|
||||
ScanKey inkeys;
|
||||
BTScanKeyPreproc xform[BTMaxStrategyNumber];
|
||||
bool test_result,
|
||||
redundant_key_kept = false;
|
||||
bool test_result;
|
||||
AttrNumber attno;
|
||||
ScanKey arrayKeyData;
|
||||
int *keyDataMap = NULL;
|
||||
@ -399,8 +388,7 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
xform[j].inkey = NULL;
|
||||
xform[j].inkeyi = -1;
|
||||
}
|
||||
else
|
||||
redundant_key_kept = true;
|
||||
/* else, cannot determine redundancy, keep both keys */
|
||||
}
|
||||
/* track number of attrs for which we have "=" keys */
|
||||
numberOfEqualCols++;
|
||||
@ -421,8 +409,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
else
|
||||
xform[BTLessStrategyNumber - 1].inkey = NULL;
|
||||
}
|
||||
else
|
||||
redundant_key_kept = true;
|
||||
}
|
||||
|
||||
/* try to keep only one of >, >= */
|
||||
@ -440,8 +426,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
else
|
||||
xform[BTGreaterStrategyNumber - 1].inkey = NULL;
|
||||
}
|
||||
else
|
||||
redundant_key_kept = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -482,6 +466,25 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
/* check strategy this key's operator corresponds to */
|
||||
j = inkey->sk_strategy - 1;
|
||||
|
||||
/* if row comparison, push it directly to the output array */
|
||||
if (inkey->sk_flags & SK_ROW_HEADER)
|
||||
{
|
||||
ScanKey outkey = &so->keyData[new_numberOfKeys++];
|
||||
|
||||
memcpy(outkey, inkey, sizeof(ScanKeyData));
|
||||
if (arrayKeyData)
|
||||
keyDataMap[new_numberOfKeys - 1] = i;
|
||||
if (numberOfEqualCols == attno - 1)
|
||||
_bt_mark_scankey_required(outkey);
|
||||
|
||||
/*
|
||||
* We don't support RowCompare using equality; such a qual would
|
||||
* mess up the numberOfEqualCols tracking.
|
||||
*/
|
||||
Assert(j != (BTEqualStrategyNumber - 1));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inkey->sk_strategy == BTEqualStrategyNumber &&
|
||||
(inkey->sk_flags & SK_SEARCHARRAY))
|
||||
{
|
||||
@ -590,8 +593,9 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
* 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.
|
||||
* always in the same order as their corresponding scan keys,
|
||||
* even with incomplete opfamilies. _bt_advance_array_keys
|
||||
* depends on this.
|
||||
*/
|
||||
ScanKey outkey = &so->keyData[new_numberOfKeys++];
|
||||
|
||||
@ -603,7 +607,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
xform[j].inkey = inkey;
|
||||
xform[j].inkeyi = i;
|
||||
xform[j].arrayidx = arrayidx;
|
||||
redundant_key_kept = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -619,15 +622,6 @@ _bt_preprocess_keys(IndexScanDesc scan)
|
||||
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 */
|
||||
}
|
||||
|
||||
@ -752,12 +746,9 @@ _bt_fix_scankey_strategy(ScanKey skey, int16 *indoption)
|
||||
*
|
||||
* 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.
|
||||
* we have to mark its first subsidiary ScanKey as required. (Subsequent
|
||||
* subsidiary ScanKeys are normally for lower-order columns, and thus
|
||||
* cannot be required, since they're after the first non-equality scankey.)
|
||||
*
|
||||
* 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
|
||||
@ -795,25 +786,12 @@ _bt_mark_scankey_required(ScanKey skey)
|
||||
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_flags & SK_ROW_MEMBER);
|
||||
Assert(subkey->sk_attno == skey->sk_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++;
|
||||
}
|
||||
subkey->sk_flags |= addflags;
|
||||
}
|
||||
}
|
||||
|
||||
@ -869,7 +847,8 @@ _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
|
||||
cmp_op;
|
||||
StrategyNumber strat;
|
||||
|
||||
Assert(!((leftarg->sk_flags | rightarg->sk_flags) & SK_ROW_MEMBER));
|
||||
Assert(!((leftarg->sk_flags | rightarg->sk_flags) &
|
||||
(SK_ROW_HEADER | SK_ROW_MEMBER)));
|
||||
|
||||
/*
|
||||
* First, deal with cases where one or both args are NULL. This should
|
||||
@ -945,16 +924,6 @@ _bt_compare_scankey_args(IndexScanDesc scan, ScanKey op,
|
||||
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)
|
||||
@ -1498,283 +1467,6 @@ _bt_skiparray_strat_increment(IndexScanDesc scan, ScanKey arraysk,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* _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
|
||||
*
|
||||
|
@ -960,51 +960,46 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
/*----------
|
||||
* Examine the scan keys to discover where we need to start the scan.
|
||||
* The selected scan keys (at most one per index column) are remembered by
|
||||
* storing their addresses into the local startKeys[] array. The final
|
||||
* startKeys[] entry's strategy is set in strat_total. (Actually, there
|
||||
* are a couple of cases where we force a less/more restrictive strategy.)
|
||||
*
|
||||
* We must use the key that was marked required (in the direction opposite
|
||||
* our own scan's) during preprocessing. Each index attribute can only
|
||||
* have one such required key. In general, the keys that we use to find
|
||||
* an initial position when scanning forwards are the same keys that end
|
||||
* the scan on the leaf level when scanning backwards (and vice-versa).
|
||||
* We want to identify the keys that can be used as starting boundaries;
|
||||
* these are =, >, or >= keys for a forward scan or =, <, <= keys for
|
||||
* a backwards scan. We can use keys for multiple attributes so long as
|
||||
* the prior attributes had only =, >= (resp. =, <=) keys. Once we accept
|
||||
* a > or < boundary or find an attribute with no boundary (which can be
|
||||
* thought of as the same as "> -infinity"), we can't use keys for any
|
||||
* attributes to its right, because it would break our simplistic notion
|
||||
* of what initial positioning strategy to use.
|
||||
*
|
||||
* When the scan keys include cross-type operators, _bt_preprocess_keys
|
||||
* may not be able to eliminate redundant keys; in such cases it will
|
||||
* arbitrarily pick a usable key for each attribute (and scan direction),
|
||||
* ensuring that there is no more than one key required in each direction.
|
||||
* We stop considering further keys once we reach the first nonrequired
|
||||
* key (which must come after all required keys), so this can't affect us.
|
||||
* may not be able to eliminate redundant keys; in such cases we will
|
||||
* arbitrarily pick a usable one for each attribute. This is correct
|
||||
* but possibly not optimal behavior. (For example, with keys like
|
||||
* "x >= 4 AND x >= 5" we would elect to scan starting at x=4 when
|
||||
* x=5 would be more efficient.) Since the situation only arises given
|
||||
* a poorly-worded query plus an incomplete opfamily, live with it.
|
||||
*
|
||||
* The required keys that we use as starting boundaries have to be =, >,
|
||||
* or >= keys for a forward scan or =, <, <= keys for a backwards scan.
|
||||
* We can use keys for multiple attributes so long as the prior attributes
|
||||
* had only =, >= (resp. =, <=) keys. These rules are very similar to the
|
||||
* rules that preprocessing used to determine which keys to mark required.
|
||||
* We cannot always use every required key as a positioning key, though.
|
||||
* Skip arrays necessitate independently applying our own rules here.
|
||||
* Skip arrays are always generally considered = array keys, but we'll
|
||||
* nevertheless treat them as inequalities at certain points of the scan.
|
||||
* When that happens, it _might_ have implications for the number of
|
||||
* required keys that we can safely use for initial positioning purposes.
|
||||
* When both equality and inequality keys appear for a single attribute
|
||||
* (again, only possible when cross-type operators appear), we *must*
|
||||
* select one of the equality keys for the starting point, because
|
||||
* _bt_checkkeys() will stop the scan as soon as an equality qual fails.
|
||||
* For example, if we have keys like "x >= 4 AND x = 10" and we elect to
|
||||
* start at x=4, we will fail and stop before reaching x=10. If multiple
|
||||
* equality quals survive preprocessing, however, it doesn't matter which
|
||||
* one we use --- by definition, they are either redundant or
|
||||
* contradictory.
|
||||
*
|
||||
* For example, a forward scan with a skip array on its leading attribute
|
||||
* (with no low_compare/high_compare) will have at least two required scan
|
||||
* keys, but we won't use any of them as boundary keys during the scan's
|
||||
* initial call here. Our positioning key during the first call here can
|
||||
* be thought of as representing "> -infinity". Similarly, if such a skip
|
||||
* array's low_compare is "a > 'foo'", then we position using "a > 'foo'"
|
||||
* during the scan's initial call here; a lower-order key such as "b = 42"
|
||||
* can't be used until the "a" array advances beyond MINVAL/low_compare.
|
||||
*
|
||||
* On the other hand, if such a skip array's low_compare was "a >= 'foo'",
|
||||
* then we _can_ use "a >= 'foo' AND b = 42" during the initial call here.
|
||||
* A subsequent call here might have us use "a = 'fop' AND b = 42". Note
|
||||
* that we treat = and >= as equivalent when scanning forwards (just as we
|
||||
* treat = and <= as equivalent when scanning backwards). We effectively
|
||||
* do the same thing (though with a distinct "a" element/value) each time.
|
||||
* In practice we rarely see any "attribute boundary key gaps" here.
|
||||
* Preprocessing can usually backfill skip array keys for any attributes
|
||||
* that were omitted from the original scan->keyData[] input keys. All
|
||||
* array keys are always considered = keys, but we'll sometimes need to
|
||||
* treat the current key value as if we were using an inequality strategy.
|
||||
* This happens with range skip arrays, which store inequality keys in the
|
||||
* array's low_compare/high_compare fields (used to find the first/last
|
||||
* set of matches, when = key will lack a usable sk_argument value).
|
||||
* These are always preferred over any redundant "standard" inequality
|
||||
* keys on the same column (per the usual rule about preferring = keys).
|
||||
* Note also that any column with an = skip array key can never have an
|
||||
* additional, contradictory = key.
|
||||
*
|
||||
* All keys (with the exception of SK_SEARCHNULL keys and SK_BT_SKIP
|
||||
* array keys whose array is "null_elem=true") imply a NOT NULL qualifier.
|
||||
@ -1016,20 +1011,21 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
* traversing a lot of null entries at the start of the scan.
|
||||
*
|
||||
* In this loop, row-comparison keys are treated the same as keys on their
|
||||
* first (leftmost) columns. We'll add all lower-order columns of the row
|
||||
* comparison that were marked required during preprocessing below.
|
||||
* first (leftmost) columns. We'll add on lower-order columns of the row
|
||||
* comparison below, if possible.
|
||||
*
|
||||
* _bt_advance_array_keys needs to know exactly how we'll reposition the
|
||||
* scan (should it opt to schedule another primitive index scan). It is
|
||||
* critical that primscans only be scheduled when they'll definitely make
|
||||
* some useful progress. _bt_advance_array_keys does this by calling
|
||||
* _bt_checkkeys routines that report whether a tuple is past the end of
|
||||
* matches for the scan's keys (given the scan's current array elements).
|
||||
* If the page's final tuple is "after the end of matches" for a scan that
|
||||
* uses the *opposite* scan direction, then it must follow that it's also
|
||||
* "before the start of matches" for the actual current scan direction.
|
||||
* It is therefore essential that all of our initial positioning rules are
|
||||
* symmetric with _bt_checkkeys's corresponding continuescan=false rule.
|
||||
* The selected scan keys (at most one per index column) are remembered by
|
||||
* storing their addresses into the local startKeys[] array.
|
||||
*
|
||||
* _bt_checkkeys/_bt_advance_array_keys decide whether and when to start
|
||||
* the next primitive index scan (for scans with array keys) based in part
|
||||
* on an understanding of how it'll enable us to reposition the scan.
|
||||
* They're directly aware of how we'll sometimes cons up an explicit
|
||||
* SK_SEARCHNOTNULL key. They'll even end primitive scans by applying a
|
||||
* symmetric "deduce NOT NULL" rule of their own. This allows top-level
|
||||
* scans to skip large groups of NULLs through repeated deductions about
|
||||
* key strictness (for a required inequality key) and whether NULLs in the
|
||||
* key's index column are stored last or first (relative to non-NULLs).
|
||||
* If you update anything here, _bt_checkkeys/_bt_advance_array_keys might
|
||||
* need to be kept in sync.
|
||||
*----------
|
||||
@ -1038,17 +1034,18 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
if (so->numberOfKeys > 0)
|
||||
{
|
||||
AttrNumber curattr;
|
||||
ScanKey bkey;
|
||||
ScanKey chosen;
|
||||
ScanKey impliesNN;
|
||||
ScanKey cur;
|
||||
|
||||
/*
|
||||
* bkey will be set to the key that preprocessing left behind as the
|
||||
* boundary key for this attribute, in this scan direction (if any)
|
||||
* chosen is the so-far-chosen key for the current attribute, if any.
|
||||
* We don't cast the decision in stone until we reach keys for the
|
||||
* next attribute.
|
||||
*/
|
||||
cur = so->keyData;
|
||||
curattr = 1;
|
||||
bkey = NULL;
|
||||
chosen = NULL;
|
||||
/* Also remember any scankey that implies a NOT NULL constraint */
|
||||
impliesNN = NULL;
|
||||
|
||||
@ -1061,29 +1058,23 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
{
|
||||
if (i >= so->numberOfKeys || cur->sk_attno != curattr)
|
||||
{
|
||||
/* Done looking for the curattr boundary key */
|
||||
Assert(bkey == NULL ||
|
||||
(bkey->sk_attno == curattr &&
|
||||
(bkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD))));
|
||||
Assert(impliesNN == NULL ||
|
||||
(impliesNN->sk_attno == curattr &&
|
||||
(impliesNN->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD))));
|
||||
|
||||
/*
|
||||
* Done looking at keys for curattr.
|
||||
*
|
||||
* If this is a scan key for a skip array whose current
|
||||
* element is MINVAL, choose low_compare (when scanning
|
||||
* backwards it'll be MAXVAL, and we'll choose high_compare).
|
||||
*
|
||||
* Note: if the array's low_compare key makes 'bkey' NULL,
|
||||
* Note: if the array's low_compare key makes 'chosen' NULL,
|
||||
* then we behave as if the array's first element is -inf,
|
||||
* except when !array->null_elem implies a usable NOT NULL
|
||||
* constraint.
|
||||
*/
|
||||
if (bkey != NULL &&
|
||||
(bkey->sk_flags & (SK_BT_MINVAL | SK_BT_MAXVAL)))
|
||||
if (chosen != NULL &&
|
||||
(chosen->sk_flags & (SK_BT_MINVAL | SK_BT_MAXVAL)))
|
||||
{
|
||||
int ikey = bkey - so->keyData;
|
||||
ScanKey skipequalitykey = bkey;
|
||||
int ikey = chosen - so->keyData;
|
||||
ScanKey skipequalitykey = chosen;
|
||||
BTArrayKeyInfo *array = NULL;
|
||||
|
||||
for (int arridx = 0; arridx < so->numArrayKeys; arridx++)
|
||||
@ -1096,35 +1087,35 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
if (ScanDirectionIsForward(dir))
|
||||
{
|
||||
Assert(!(skipequalitykey->sk_flags & SK_BT_MAXVAL));
|
||||
bkey = array->low_compare;
|
||||
chosen = array->low_compare;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(!(skipequalitykey->sk_flags & SK_BT_MINVAL));
|
||||
bkey = array->high_compare;
|
||||
chosen = array->high_compare;
|
||||
}
|
||||
|
||||
Assert(bkey == NULL ||
|
||||
bkey->sk_attno == skipequalitykey->sk_attno);
|
||||
Assert(chosen == NULL ||
|
||||
chosen->sk_attno == skipequalitykey->sk_attno);
|
||||
|
||||
if (!array->null_elem)
|
||||
impliesNN = skipequalitykey;
|
||||
else
|
||||
Assert(bkey == NULL && impliesNN == NULL);
|
||||
Assert(chosen == NULL && impliesNN == NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we didn't find a usable boundary key, see if we can
|
||||
* deduce a NOT NULL key
|
||||
*/
|
||||
if (bkey == NULL && impliesNN != NULL &&
|
||||
if (chosen == NULL && impliesNN != NULL &&
|
||||
((impliesNN->sk_flags & SK_BT_NULLS_FIRST) ?
|
||||
ScanDirectionIsForward(dir) :
|
||||
ScanDirectionIsBackward(dir)))
|
||||
{
|
||||
/* Yes, so build the key in notnullkeys[keysz] */
|
||||
bkey = ¬nullkeys[keysz];
|
||||
ScanKeyEntryInitialize(bkey,
|
||||
chosen = ¬nullkeys[keysz];
|
||||
ScanKeyEntryInitialize(chosen,
|
||||
(SK_SEARCHNOTNULL | SK_ISNULL |
|
||||
(impliesNN->sk_flags &
|
||||
(SK_BT_DESC | SK_BT_NULLS_FIRST))),
|
||||
@ -1139,12 +1130,12 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
}
|
||||
|
||||
/*
|
||||
* If preprocessing didn't leave a usable boundary key, quit;
|
||||
* else save the boundary key pointer in startKeys[]
|
||||
* If we still didn't find a usable boundary key, quit; else
|
||||
* save the boundary key pointer in startKeys.
|
||||
*/
|
||||
if (bkey == NULL)
|
||||
if (chosen == NULL)
|
||||
break;
|
||||
startKeys[keysz++] = bkey;
|
||||
startKeys[keysz++] = chosen;
|
||||
|
||||
/*
|
||||
* We can only consider adding more boundary keys when the one
|
||||
@ -1152,7 +1143,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
* (during backwards scans we can only do so when the key that
|
||||
* we just added to startKeys[] uses the = or <= strategy)
|
||||
*/
|
||||
strat_total = bkey->sk_strategy;
|
||||
strat_total = chosen->sk_strategy;
|
||||
if (strat_total == BTGreaterStrategyNumber ||
|
||||
strat_total == BTLessStrategyNumber)
|
||||
break;
|
||||
@ -1163,19 +1154,19 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
* make strat_total > or < (and stop adding boundary keys).
|
||||
* This can only happen with opclasses that lack skip support.
|
||||
*/
|
||||
if (bkey->sk_flags & (SK_BT_NEXT | SK_BT_PRIOR))
|
||||
if (chosen->sk_flags & (SK_BT_NEXT | SK_BT_PRIOR))
|
||||
{
|
||||
Assert(bkey->sk_flags & SK_BT_SKIP);
|
||||
Assert(chosen->sk_flags & SK_BT_SKIP);
|
||||
Assert(strat_total == BTEqualStrategyNumber);
|
||||
|
||||
if (ScanDirectionIsForward(dir))
|
||||
{
|
||||
Assert(!(bkey->sk_flags & SK_BT_PRIOR));
|
||||
Assert(!(chosen->sk_flags & SK_BT_PRIOR));
|
||||
strat_total = BTGreaterStrategyNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(!(bkey->sk_flags & SK_BT_NEXT));
|
||||
Assert(!(chosen->sk_flags & SK_BT_NEXT));
|
||||
strat_total = BTLessStrategyNumber;
|
||||
}
|
||||
|
||||
@ -1189,30 +1180,24 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
/*
|
||||
* Done if that was the last scan key output by preprocessing.
|
||||
* Also done if we've now examined all keys marked required.
|
||||
* Also done if there is a gap index attribute that lacks a
|
||||
* usable key (only possible when preprocessing was unable to
|
||||
* generate a skip array key to "fill in the gap").
|
||||
*/
|
||||
if (i >= so->numberOfKeys ||
|
||||
!(cur->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)))
|
||||
cur->sk_attno != curattr + 1)
|
||||
break;
|
||||
|
||||
/*
|
||||
* Reset for next attr.
|
||||
*/
|
||||
Assert(cur->sk_attno == curattr + 1);
|
||||
curattr = cur->sk_attno;
|
||||
bkey = NULL;
|
||||
chosen = NULL;
|
||||
impliesNN = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we've located the starting boundary key for curattr, we have
|
||||
* no interest in curattr's other required key
|
||||
*/
|
||||
if (bkey != NULL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Is this key the starting boundary key for curattr?
|
||||
* Can we use this key as a starting boundary for this attr?
|
||||
*
|
||||
* If not, does it imply a NOT NULL constraint? (Because
|
||||
* SK_SEARCHNULL keys are always assigned BTEqualStrategyNumber,
|
||||
@ -1222,20 +1207,27 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
{
|
||||
case BTLessStrategyNumber:
|
||||
case BTLessEqualStrategyNumber:
|
||||
if (ScanDirectionIsBackward(dir))
|
||||
bkey = cur;
|
||||
else if (impliesNN == NULL)
|
||||
impliesNN = cur;
|
||||
if (chosen == NULL)
|
||||
{
|
||||
if (ScanDirectionIsBackward(dir))
|
||||
chosen = cur;
|
||||
else
|
||||
impliesNN = cur;
|
||||
}
|
||||
break;
|
||||
case BTEqualStrategyNumber:
|
||||
bkey = cur;
|
||||
/* override any non-equality choice */
|
||||
chosen = cur;
|
||||
break;
|
||||
case BTGreaterEqualStrategyNumber:
|
||||
case BTGreaterStrategyNumber:
|
||||
if (ScanDirectionIsForward(dir))
|
||||
bkey = cur;
|
||||
else if (impliesNN == NULL)
|
||||
impliesNN = cur;
|
||||
if (chosen == NULL)
|
||||
{
|
||||
if (ScanDirectionIsForward(dir))
|
||||
chosen = cur;
|
||||
else
|
||||
impliesNN = cur;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1261,18 +1253,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
Assert(keysz <= INDEX_MAX_KEYS);
|
||||
for (int i = 0; i < keysz; i++)
|
||||
{
|
||||
ScanKey bkey = startKeys[i];
|
||||
ScanKey cur = startKeys[i];
|
||||
|
||||
Assert(bkey->sk_attno == i + 1);
|
||||
Assert(cur->sk_attno == i + 1);
|
||||
|
||||
if (bkey->sk_flags & SK_ROW_HEADER)
|
||||
if (cur->sk_flags & SK_ROW_HEADER)
|
||||
{
|
||||
/*
|
||||
* Row comparison header: look to the first row member instead
|
||||
*/
|
||||
ScanKey subkey = (ScanKey) DatumGetPointer(bkey->sk_argument);
|
||||
bool loosen_strat = false,
|
||||
tighten_strat = false;
|
||||
ScanKey subkey = (ScanKey) DatumGetPointer(cur->sk_argument);
|
||||
|
||||
/*
|
||||
* Cannot be a NULL in the first row member: _bt_preprocess_keys
|
||||
@ -1280,18 +1270,9 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
* ever getting this far
|
||||
*/
|
||||
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
||||
Assert(subkey->sk_attno == bkey->sk_attno);
|
||||
Assert(subkey->sk_attno == cur->sk_attno);
|
||||
Assert(!(subkey->sk_flags & SK_ISNULL));
|
||||
|
||||
/*
|
||||
* This is either a > or >= key (during backwards scans it is
|
||||
* either < or <=) that was marked required during preprocessing.
|
||||
* Later so->keyData[] keys can't have been marked required, so
|
||||
* our row compare header key must be the final startKeys[] entry.
|
||||
*/
|
||||
Assert(subkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD));
|
||||
Assert(i == keysz - 1);
|
||||
|
||||
/*
|
||||
* The member scankeys are already in insertion format (ie, they
|
||||
* have sk_func = 3-way-comparison function)
|
||||
@ -1299,141 +1280,112 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
memcpy(inskey.scankeys + i, subkey, sizeof(ScanKeyData));
|
||||
|
||||
/*
|
||||
* Now look to later row compare members.
|
||||
*
|
||||
* If there's an "index attribute gap" between two row compare
|
||||
* members, the second member won't have been marked required, and
|
||||
* so can't be used as a starting boundary key here. The part of
|
||||
* the row comparison that we do still use has to be treated as a
|
||||
* ">=" or "<=" condition. For example, a qual "(a, c) > (1, 42)"
|
||||
* with an omitted intervening index attribute "b" will use an
|
||||
* insertion scan key "a >= 1". Even the first "a = 1" tuple on
|
||||
* the leaf level might satisfy the row compare qual.
|
||||
*
|
||||
* We're able to use a _more_ restrictive strategy when we reach a
|
||||
* NULL row compare member, since they're always unsatisfiable.
|
||||
* For example, a qual "(a, b, c) >= (1, NULL, 77)" will use an
|
||||
* insertion scan key "a > 1". All tuples where "a = 1" cannot
|
||||
* possibly satisfy the row compare qual, so this is safe.
|
||||
* If the row comparison is the last positioning key we accepted,
|
||||
* try to add additional keys from the lower-order row members.
|
||||
* (If we accepted independent conditions on additional index
|
||||
* columns, we use those instead --- doesn't seem worth trying to
|
||||
* determine which is more restrictive.) Note that this is OK
|
||||
* even if the row comparison is of ">" or "<" type, because the
|
||||
* condition applied to all but the last row member is effectively
|
||||
* ">=" or "<=", and so the extra keys don't break the positioning
|
||||
* scheme. But, by the same token, if we aren't able to use all
|
||||
* the row members, then the part of the row comparison that we
|
||||
* did use has to be treated as just a ">=" or "<=" condition, and
|
||||
* so we'd better adjust strat_total accordingly.
|
||||
*/
|
||||
Assert(!(subkey->sk_flags & SK_ROW_END));
|
||||
for (;;)
|
||||
if (i == keysz - 1)
|
||||
{
|
||||
subkey++;
|
||||
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
||||
bool used_all_subkeys = false;
|
||||
|
||||
if (subkey->sk_flags & SK_ISNULL)
|
||||
Assert(!(subkey->sk_flags & SK_ROW_END));
|
||||
for (;;)
|
||||
{
|
||||
/*
|
||||
* NULL member key, can only use earlier keys.
|
||||
*
|
||||
* We deliberately avoid checking if this key is marked
|
||||
* required. All earlier keys are required, and this key
|
||||
* is unsatisfiable either way, so we can't miss anything.
|
||||
*/
|
||||
tighten_strat = true;
|
||||
break;
|
||||
subkey++;
|
||||
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
||||
if (subkey->sk_attno != keysz + 1)
|
||||
break; /* out-of-sequence, can't use it */
|
||||
if (subkey->sk_strategy != cur->sk_strategy)
|
||||
break; /* wrong direction, can't use it */
|
||||
if (subkey->sk_flags & SK_ISNULL)
|
||||
break; /* can't use null keys */
|
||||
Assert(keysz < INDEX_MAX_KEYS);
|
||||
memcpy(inskey.scankeys + keysz, subkey,
|
||||
sizeof(ScanKeyData));
|
||||
keysz++;
|
||||
if (subkey->sk_flags & SK_ROW_END)
|
||||
{
|
||||
used_all_subkeys = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(subkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)))
|
||||
if (!used_all_subkeys)
|
||||
{
|
||||
/* nonrequired member key, can only use earlier keys */
|
||||
loosen_strat = true;
|
||||
break;
|
||||
switch (strat_total)
|
||||
{
|
||||
case BTLessStrategyNumber:
|
||||
strat_total = BTLessEqualStrategyNumber;
|
||||
break;
|
||||
case BTGreaterStrategyNumber:
|
||||
strat_total = BTGreaterEqualStrategyNumber;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert(subkey->sk_attno == keysz + 1);
|
||||
Assert(subkey->sk_strategy == bkey->sk_strategy);
|
||||
Assert(keysz < INDEX_MAX_KEYS);
|
||||
|
||||
memcpy(inskey.scankeys + keysz, subkey,
|
||||
sizeof(ScanKeyData));
|
||||
keysz++;
|
||||
if (subkey->sk_flags & SK_ROW_END)
|
||||
break;
|
||||
break; /* done with outer loop */
|
||||
}
|
||||
Assert(!(loosen_strat && tighten_strat));
|
||||
if (loosen_strat)
|
||||
{
|
||||
/* Use less restrictive strategy (and fewer member keys) */
|
||||
switch (strat_total)
|
||||
{
|
||||
case BTLessStrategyNumber:
|
||||
strat_total = BTLessEqualStrategyNumber;
|
||||
break;
|
||||
case BTGreaterStrategyNumber:
|
||||
strat_total = BTGreaterEqualStrategyNumber;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tighten_strat)
|
||||
{
|
||||
/* Use more restrictive strategy (and fewer member keys) */
|
||||
switch (strat_total)
|
||||
{
|
||||
case BTLessEqualStrategyNumber:
|
||||
strat_total = BTLessStrategyNumber;
|
||||
break;
|
||||
case BTGreaterEqualStrategyNumber:
|
||||
strat_total = BTGreaterStrategyNumber;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* done adding to inskey (row comparison keys always come last) */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ordinary comparison key/search-style key.
|
||||
*
|
||||
* Transform the search-style scan key to an insertion scan key by
|
||||
* replacing the sk_func with the appropriate btree 3-way-comparison
|
||||
* function.
|
||||
*
|
||||
* If scankey operator is not a cross-type comparison, we can use the
|
||||
* cached comparison function; otherwise gotta look it up in the
|
||||
* catalogs. (That can't lead to infinite recursion, since no
|
||||
* indexscan initiated by syscache lookup will use cross-data-type
|
||||
* operators.)
|
||||
*
|
||||
* We support the convention that sk_subtype == InvalidOid means the
|
||||
* opclass input type; this hack simplifies life for ScanKeyInit().
|
||||
*/
|
||||
if (bkey->sk_subtype == rel->rd_opcintype[i] ||
|
||||
bkey->sk_subtype == InvalidOid)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
|
||||
procinfo = index_getprocinfo(rel, bkey->sk_attno, BTORDER_PROC);
|
||||
ScanKeyEntryInitializeWithInfo(inskey.scankeys + i,
|
||||
bkey->sk_flags,
|
||||
bkey->sk_attno,
|
||||
InvalidStrategy,
|
||||
bkey->sk_subtype,
|
||||
bkey->sk_collation,
|
||||
procinfo,
|
||||
bkey->sk_argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegProcedure cmp_proc;
|
||||
/*
|
||||
* Ordinary comparison key. Transform the search-style scan key
|
||||
* to an insertion scan key by replacing the sk_func with the
|
||||
* appropriate btree comparison function.
|
||||
*
|
||||
* If scankey operator is not a cross-type comparison, we can use
|
||||
* the cached comparison function; otherwise gotta look it up in
|
||||
* the catalogs. (That can't lead to infinite recursion, since no
|
||||
* indexscan initiated by syscache lookup will use cross-data-type
|
||||
* operators.)
|
||||
*
|
||||
* We support the convention that sk_subtype == InvalidOid means
|
||||
* the opclass input type; this is a hack to simplify life for
|
||||
* ScanKeyInit().
|
||||
*/
|
||||
if (cur->sk_subtype == rel->rd_opcintype[i] ||
|
||||
cur->sk_subtype == InvalidOid)
|
||||
{
|
||||
FmgrInfo *procinfo;
|
||||
|
||||
cmp_proc = get_opfamily_proc(rel->rd_opfamily[i],
|
||||
rel->rd_opcintype[i],
|
||||
bkey->sk_subtype, BTORDER_PROC);
|
||||
if (!RegProcedureIsValid(cmp_proc))
|
||||
elog(ERROR, "missing support function %d(%u,%u) for attribute %d of index \"%s\"",
|
||||
BTORDER_PROC, rel->rd_opcintype[i], bkey->sk_subtype,
|
||||
bkey->sk_attno, RelationGetRelationName(rel));
|
||||
ScanKeyEntryInitialize(inskey.scankeys + i,
|
||||
bkey->sk_flags,
|
||||
bkey->sk_attno,
|
||||
InvalidStrategy,
|
||||
bkey->sk_subtype,
|
||||
bkey->sk_collation,
|
||||
cmp_proc,
|
||||
bkey->sk_argument);
|
||||
procinfo = index_getprocinfo(rel, cur->sk_attno, BTORDER_PROC);
|
||||
ScanKeyEntryInitializeWithInfo(inskey.scankeys + i,
|
||||
cur->sk_flags,
|
||||
cur->sk_attno,
|
||||
InvalidStrategy,
|
||||
cur->sk_subtype,
|
||||
cur->sk_collation,
|
||||
procinfo,
|
||||
cur->sk_argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegProcedure cmp_proc;
|
||||
|
||||
cmp_proc = get_opfamily_proc(rel->rd_opfamily[i],
|
||||
rel->rd_opcintype[i],
|
||||
cur->sk_subtype,
|
||||
BTORDER_PROC);
|
||||
if (!RegProcedureIsValid(cmp_proc))
|
||||
elog(ERROR, "missing support function %d(%u,%u) for attribute %d of index \"%s\"",
|
||||
BTORDER_PROC, rel->rd_opcintype[i], cur->sk_subtype,
|
||||
cur->sk_attno, RelationGetRelationName(rel));
|
||||
ScanKeyEntryInitialize(inskey.scankeys + i,
|
||||
cur->sk_flags,
|
||||
cur->sk_attno,
|
||||
InvalidStrategy,
|
||||
cur->sk_subtype,
|
||||
cur->sk_collation,
|
||||
cmp_proc,
|
||||
cur->sk_argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1522,8 +1474,6 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
if (!BufferIsValid(so->currPos.buf))
|
||||
{
|
||||
Assert(!so->needPrimScan);
|
||||
|
||||
/*
|
||||
* We only get here if the index is completely empty. Lock relation
|
||||
* because nothing finer to lock exists. Without a buffer lock, it's
|
||||
@ -1542,6 +1492,7 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
|
||||
if (!BufferIsValid(so->currPos.buf))
|
||||
{
|
||||
Assert(!so->needPrimScan);
|
||||
_bt_parallel_done(scan);
|
||||
return false;
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ static bool _bt_array_decrement(Relation rel, ScanKey skey, BTArrayKeyInfo *arra
|
||||
static bool _bt_array_increment(Relation rel, ScanKey skey, BTArrayKeyInfo *array);
|
||||
static bool _bt_advance_array_keys_increment(IndexScanDesc scan, ScanDirection dir,
|
||||
bool *skip_array_set);
|
||||
static void _bt_rewind_nonrequired_arrays(IndexScanDesc scan, ScanDirection dir);
|
||||
static bool _bt_tuple_before_array_skeys(IndexScanDesc scan, ScanDirection dir,
|
||||
IndexTuple tuple, TupleDesc tupdesc, int tupnatts,
|
||||
bool readpagetup, int sktrig, bool *scanBehind);
|
||||
@ -51,6 +52,7 @@ static bool _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
|
||||
IndexTuple tuple, int tupnatts, TupleDesc tupdesc,
|
||||
int sktrig, bool sktrig_required);
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
static bool _bt_verify_arrays_bt_first(IndexScanDesc scan, ScanDirection dir);
|
||||
static bool _bt_verify_keys_with_arraykeys(IndexScanDesc scan);
|
||||
#endif
|
||||
static bool _bt_oppodir_checkkeys(IndexScanDesc scan, ScanDirection dir,
|
||||
@ -1032,6 +1034,73 @@ _bt_advance_array_keys_increment(IndexScanDesc scan, ScanDirection dir,
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* _bt_rewind_nonrequired_arrays() -- Rewind SAOP arrays not marked required
|
||||
*
|
||||
* Called when _bt_advance_array_keys decides to start a new primitive index
|
||||
* scan on the basis of the current scan position being before the position
|
||||
* that _bt_first is capable of repositioning the scan to by applying an
|
||||
* inequality operator required in the opposite-to-scan direction only.
|
||||
*
|
||||
* Although equality strategy scan keys (for both arrays and non-arrays alike)
|
||||
* are either marked required in both directions or in neither direction,
|
||||
* there is a sense in which non-required arrays behave like required arrays.
|
||||
* With a qual such as "WHERE a IN (100, 200) AND b >= 3 AND c IN (5, 6, 7)",
|
||||
* the scan key on "c" is non-required, but nevertheless enables positioning
|
||||
* the scan at the first tuple >= "(100, 3, 5)" on the leaf level during the
|
||||
* first descent of the tree by _bt_first. Later on, there could also be a
|
||||
* second descent, that places the scan right before tuples >= "(200, 3, 5)".
|
||||
* _bt_first must never be allowed to build an insertion scan key whose "c"
|
||||
* entry is set to a value other than 5, the "c" array's first element/value.
|
||||
* (Actually, it's the first in the current scan direction. This example uses
|
||||
* a forward scan.)
|
||||
*
|
||||
* Calling here resets the array scan key elements for the scan's non-required
|
||||
* arrays. This is strictly necessary for correctness in a subset of cases
|
||||
* involving "required in opposite direction"-triggered primitive index scans.
|
||||
* Not all callers are at risk of _bt_first using a non-required array like
|
||||
* this, but advancement always resets the arrays when another primitive scan
|
||||
* is scheduled, just to keep things simple. Array advancement even makes
|
||||
* sure to reset non-required arrays during scans that have no inequalities.
|
||||
* (Advancement still won't call here when there are no inequalities, though
|
||||
* that's just because it's all handled indirectly instead.)
|
||||
*
|
||||
* Note: _bt_verify_arrays_bt_first is called by an assertion to enforce that
|
||||
* everybody got this right.
|
||||
*
|
||||
* Note: In practice almost all SAOP arrays are marked required during
|
||||
* preprocessing (if necessary by generating skip arrays). It is hardly ever
|
||||
* truly necessary to call here, but consistently doing so is simpler.
|
||||
*/
|
||||
static void
|
||||
_bt_rewind_nonrequired_arrays(IndexScanDesc scan, ScanDirection dir)
|
||||
{
|
||||
Relation rel = scan->indexRelation;
|
||||
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
||||
int arrayidx = 0;
|
||||
|
||||
for (int ikey = 0; ikey < so->numberOfKeys; ikey++)
|
||||
{
|
||||
ScanKey cur = so->keyData + ikey;
|
||||
BTArrayKeyInfo *array = NULL;
|
||||
|
||||
if (!(cur->sk_flags & SK_SEARCHARRAY) ||
|
||||
cur->sk_strategy != BTEqualStrategyNumber)
|
||||
continue;
|
||||
|
||||
array = &so->arrayKeys[arrayidx++];
|
||||
Assert(array->scan_key == ikey);
|
||||
|
||||
if ((cur->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)))
|
||||
continue;
|
||||
|
||||
Assert(array->num_elems != -1); /* No non-required skip arrays */
|
||||
|
||||
_bt_array_set_low_or_high(rel, cur, array,
|
||||
ScanDirectionIsForward(dir));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* _bt_tuple_before_array_skeys() -- too early to advance required arrays?
|
||||
*
|
||||
@ -1311,6 +1380,8 @@ _bt_start_prim_scan(IndexScanDesc scan, ScanDirection dir)
|
||||
*/
|
||||
if (so->needPrimScan)
|
||||
{
|
||||
Assert(_bt_verify_arrays_bt_first(scan, dir));
|
||||
|
||||
/*
|
||||
* Flag was set -- must call _bt_first again, which will reset the
|
||||
* scan's needPrimScan flag
|
||||
@ -1936,7 +2007,14 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
|
||||
*/
|
||||
else if (has_required_opposite_direction_only && pstate->finaltup &&
|
||||
unlikely(!_bt_oppodir_checkkeys(scan, dir, pstate->finaltup)))
|
||||
{
|
||||
/*
|
||||
* Make sure that any SAOP arrays that were not marked required by
|
||||
* preprocessing are reset to their first element for this direction
|
||||
*/
|
||||
_bt_rewind_nonrequired_arrays(scan, dir);
|
||||
goto new_prim_scan;
|
||||
}
|
||||
|
||||
continue_scan:
|
||||
|
||||
@ -1967,6 +2045,8 @@ continue_scan:
|
||||
*/
|
||||
so->oppositeDirCheck = has_required_opposite_direction_only;
|
||||
|
||||
_bt_rewind_nonrequired_arrays(scan, dir);
|
||||
|
||||
/*
|
||||
* skip by setting "look ahead" mechanism's offnum for forwards scans
|
||||
* (backwards scans check scanBehind flag directly instead)
|
||||
@ -2062,6 +2142,48 @@ end_toplevel_scan:
|
||||
}
|
||||
|
||||
#ifdef USE_ASSERT_CHECKING
|
||||
/*
|
||||
* Verify that the scan's qual state matches what we expect at the point that
|
||||
* _bt_start_prim_scan is about to start a just-scheduled new primitive scan.
|
||||
*
|
||||
* We enforce a rule against non-required array scan keys: they must start out
|
||||
* with whatever element is the first for the scan's current scan direction.
|
||||
* See _bt_rewind_nonrequired_arrays comments for an explanation.
|
||||
*/
|
||||
static bool
|
||||
_bt_verify_arrays_bt_first(IndexScanDesc scan, ScanDirection dir)
|
||||
{
|
||||
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
||||
int arrayidx = 0;
|
||||
|
||||
for (int ikey = 0; ikey < so->numberOfKeys; ikey++)
|
||||
{
|
||||
ScanKey cur = so->keyData + ikey;
|
||||
BTArrayKeyInfo *array = NULL;
|
||||
int first_elem_dir;
|
||||
|
||||
if (!(cur->sk_flags & SK_SEARCHARRAY) ||
|
||||
cur->sk_strategy != BTEqualStrategyNumber)
|
||||
continue;
|
||||
|
||||
array = &so->arrayKeys[arrayidx++];
|
||||
|
||||
if (((cur->sk_flags & SK_BT_REQFWD) && ScanDirectionIsForward(dir)) ||
|
||||
((cur->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsBackward(dir)))
|
||||
continue;
|
||||
|
||||
if (ScanDirectionIsForward(dir))
|
||||
first_elem_dir = 0;
|
||||
else
|
||||
first_elem_dir = array->num_elems - 1;
|
||||
|
||||
if (array->cur_elem != first_elem_dir)
|
||||
return false;
|
||||
}
|
||||
|
||||
return _bt_verify_keys_with_arraykeys(scan);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the scan's "so->keyData[]" scan keys are in agreement with
|
||||
* its array key state
|
||||
@ -2072,7 +2194,6 @@ _bt_verify_keys_with_arraykeys(IndexScanDesc scan)
|
||||
BTScanOpaque so = (BTScanOpaque) scan->opaque;
|
||||
int last_sk_attno = InvalidAttrNumber,
|
||||
arrayidx = 0;
|
||||
bool nonrequiredseen = false;
|
||||
|
||||
if (!so->qual_ok)
|
||||
return false;
|
||||
@ -2096,16 +2217,8 @@ _bt_verify_keys_with_arraykeys(IndexScanDesc scan)
|
||||
if (array->num_elems != -1 &&
|
||||
cur->sk_argument != array->elem_values[array->cur_elem])
|
||||
return false;
|
||||
if (cur->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD))
|
||||
{
|
||||
if (last_sk_attno > cur->sk_attno)
|
||||
return false;
|
||||
if (nonrequiredseen)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
nonrequiredseen = true;
|
||||
|
||||
if (last_sk_attno > cur->sk_attno)
|
||||
return false;
|
||||
last_sk_attno = cur->sk_attno;
|
||||
}
|
||||
|
||||
@ -2438,12 +2551,37 @@ _bt_set_startikey(IndexScanDesc scan, BTReadPageState *pstate)
|
||||
if (!(key->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)))
|
||||
{
|
||||
/* Scan key isn't marked required (corner case) */
|
||||
Assert(!(key->sk_flags & SK_ROW_HEADER));
|
||||
break; /* unsafe */
|
||||
}
|
||||
if (key->sk_flags & SK_ROW_HEADER)
|
||||
{
|
||||
/* RowCompare inequalities currently aren't supported */
|
||||
break; /* "unsafe" */
|
||||
/*
|
||||
* RowCompare inequality.
|
||||
*
|
||||
* Only the first subkey from a RowCompare can ever be marked
|
||||
* required (that happens when the row header is marked required).
|
||||
* There is no simple, general way for us to transitively deduce
|
||||
* whether or not every tuple on the page satisfies a RowCompare
|
||||
* key based only on firsttup and lasttup -- so we just give up.
|
||||
*/
|
||||
if (!start_past_saop_eq && !so->skipScan)
|
||||
break; /* unsafe to go further */
|
||||
|
||||
/*
|
||||
* We have to be even more careful with RowCompares that come
|
||||
* after an array: we assume it's unsafe to even bypass the array.
|
||||
* Calling _bt_start_array_keys to recover the scan's arrays
|
||||
* following use of forcenonrequired mode isn't compatible with
|
||||
* _bt_check_rowcompare's continuescan=false behavior with NULL
|
||||
* row compare members. _bt_advance_array_keys must not make a
|
||||
* decision on the basis of a key not being satisfied in the
|
||||
* opposite-to-scan direction until the scan reaches a leaf page
|
||||
* where the same key begins to be satisfied in scan direction.
|
||||
* The _bt_first !used_all_subkeys behavior makes this limitation
|
||||
* hard to work around some other way.
|
||||
*/
|
||||
return; /* completely unsafe to set pstate.startikey */
|
||||
}
|
||||
if (key->sk_strategy != BTEqualStrategyNumber)
|
||||
{
|
||||
@ -2940,7 +3078,76 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
|
||||
|
||||
Assert(subkey->sk_flags & SK_ROW_MEMBER);
|
||||
|
||||
/* When a NULL row member is compared, the row never matches */
|
||||
if (subkey->sk_attno > tupnatts)
|
||||
{
|
||||
/*
|
||||
* This attribute is truncated (must be high key). The value for
|
||||
* this attribute in the first non-pivot tuple on the page to the
|
||||
* right could be any possible value. Assume that truncated
|
||||
* attribute passes the qual.
|
||||
*/
|
||||
Assert(BTreeTupleIsPivot(tuple));
|
||||
cmpresult = 0;
|
||||
if (subkey->sk_flags & SK_ROW_END)
|
||||
break;
|
||||
subkey++;
|
||||
continue;
|
||||
}
|
||||
|
||||
datum = index_getattr(tuple,
|
||||
subkey->sk_attno,
|
||||
tupdesc,
|
||||
&isNull);
|
||||
|
||||
if (isNull)
|
||||
{
|
||||
if (forcenonrequired)
|
||||
{
|
||||
/* treating scan's keys as non-required */
|
||||
}
|
||||
else if (subkey->sk_flags & SK_BT_NULLS_FIRST)
|
||||
{
|
||||
/*
|
||||
* Since NULLs are sorted before non-NULLs, we know we have
|
||||
* reached the lower limit of the range of values for this
|
||||
* index attr. On a backward scan, we can stop if this qual
|
||||
* is one of the "must match" subset. We can stop regardless
|
||||
* of whether the qual is > or <, so long as it's required,
|
||||
* because it's not possible for any future tuples to pass. On
|
||||
* a forward scan, however, we must keep going, because we may
|
||||
* have initially positioned to the start of the index.
|
||||
* (_bt_advance_array_keys also relies on this behavior during
|
||||
* forward scans.)
|
||||
*/
|
||||
if ((subkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)) &&
|
||||
ScanDirectionIsBackward(dir))
|
||||
*continuescan = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Since NULLs are sorted after non-NULLs, we know we have
|
||||
* reached the upper limit of the range of values for this
|
||||
* index attr. On a forward scan, we can stop if this qual is
|
||||
* one of the "must match" subset. We can stop regardless of
|
||||
* whether the qual is > or <, so long as it's required,
|
||||
* because it's not possible for any future tuples to pass. On
|
||||
* a backward scan, however, we must keep going, because we
|
||||
* may have initially positioned to the end of the index.
|
||||
* (_bt_advance_array_keys also relies on this behavior during
|
||||
* backward scans.)
|
||||
*/
|
||||
if ((subkey->sk_flags & (SK_BT_REQFWD | SK_BT_REQBKWD)) &&
|
||||
ScanDirectionIsForward(dir))
|
||||
*continuescan = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* In any case, this indextuple doesn't match the qual.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subkey->sk_flags & SK_ISNULL)
|
||||
{
|
||||
/*
|
||||
@ -2965,114 +3172,6 @@ _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, int tupnatts,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (subkey->sk_attno > tupnatts)
|
||||
{
|
||||
/*
|
||||
* This attribute is truncated (must be high key). The value for
|
||||
* this attribute in the first non-pivot tuple on the page to the
|
||||
* right could be any possible value. Assume that truncated
|
||||
* attribute passes the qual.
|
||||
*/
|
||||
Assert(BTreeTupleIsPivot(tuple));
|
||||
return true;
|
||||
}
|
||||
|
||||
datum = index_getattr(tuple,
|
||||
subkey->sk_attno,
|
||||
tupdesc,
|
||||
&isNull);
|
||||
|
||||
if (isNull)
|
||||
{
|
||||
int reqflags;
|
||||
|
||||
if (forcenonrequired)
|
||||
{
|
||||
/* treating scan's keys as non-required */
|
||||
}
|
||||
else if (subkey->sk_flags & SK_BT_NULLS_FIRST)
|
||||
{
|
||||
/*
|
||||
* Since NULLs are sorted before non-NULLs, we know we have
|
||||
* reached the lower limit of the range of values for this
|
||||
* index attr. On a backward scan, we can stop if this qual
|
||||
* is one of the "must match" subset. However, on a forwards
|
||||
* scan, we must keep going, because we may have initially
|
||||
* positioned to the start of the index.
|
||||
*
|
||||
* All required NULLS FIRST > row members can use NULL tuple
|
||||
* values to end backwards scans, just like with other values.
|
||||
* A qual "WHERE (a, b, c) > (9, 42, 'foo')" can terminate a
|
||||
* backwards scan upon reaching the index's rightmost "a = 9"
|
||||
* tuple whose "b" column contains a NULL (if not sooner).
|
||||
* Since "b" is NULLS FIRST, we can treat its NULLs as "<" 42.
|
||||
*/
|
||||
reqflags = SK_BT_REQBKWD;
|
||||
|
||||
/*
|
||||
* When a most significant required NULLS FIRST < row compare
|
||||
* member sees NULL tuple values during a backwards scan, it
|
||||
* signals the end of matches for the whole row compare/scan.
|
||||
* A qual "WHERE (a, b, c) < (9, 42, 'foo')" will terminate a
|
||||
* backwards scan upon reaching the rightmost tuple whose "a"
|
||||
* column has a NULL. The "a" NULL value is "<" 9, and yet
|
||||
* our < row compare will still end the scan. (This isn't
|
||||
* safe with later/lower-order row members. Notice that it
|
||||
* can only happen with an "a" NULL some time after the scan
|
||||
* completely stops needing to use its "b" and "c" members.)
|
||||
*/
|
||||
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
|
||||
reqflags |= SK_BT_REQFWD; /* safe, first row member */
|
||||
|
||||
if ((subkey->sk_flags & reqflags) &&
|
||||
ScanDirectionIsBackward(dir))
|
||||
*continuescan = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Since NULLs are sorted after non-NULLs, we know we have
|
||||
* reached the upper limit of the range of values for this
|
||||
* index attr. On a forward scan, we can stop if this qual is
|
||||
* one of the "must match" subset. However, on a backward
|
||||
* scan, we must keep going, because we may have initially
|
||||
* positioned to the end of the index.
|
||||
*
|
||||
* All required NULLS LAST < row members can use NULL tuple
|
||||
* values to end forwards scans, just like with other values.
|
||||
* A qual "WHERE (a, b, c) < (9, 42, 'foo')" can terminate a
|
||||
* forwards scan upon reaching the index's leftmost "a = 9"
|
||||
* tuple whose "b" column contains a NULL (if not sooner).
|
||||
* Since "b" is NULLS LAST, we can treat its NULLs as ">" 42.
|
||||
*/
|
||||
reqflags = SK_BT_REQFWD;
|
||||
|
||||
/*
|
||||
* When a most significant required NULLS LAST > row compare
|
||||
* member sees NULL tuple values during a forwards scan, it
|
||||
* signals the end of matches for the whole row compare/scan.
|
||||
* A qual "WHERE (a, b, c) > (9, 42, 'foo')" will terminate a
|
||||
* forwards scan upon reaching the leftmost tuple whose "a"
|
||||
* column has a NULL. The "a" NULL value is ">" 9, and yet
|
||||
* our > row compare will end the scan. (This isn't safe with
|
||||
* later/lower-order row members. Notice that it can only
|
||||
* happen with an "a" NULL some time after the scan completely
|
||||
* stops needing to use its "b" and "c" members.)
|
||||
*/
|
||||
if (subkey == (ScanKey) DatumGetPointer(skey->sk_argument))
|
||||
reqflags |= SK_BT_REQBKWD; /* safe, first row member */
|
||||
|
||||
if ((subkey->sk_flags & reqflags) &&
|
||||
ScanDirectionIsForward(dir))
|
||||
*continuescan = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* In any case, this indextuple doesn't match the qual.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Perform the test --- three-way comparison not bool operator */
|
||||
cmpresult = DatumGetInt32(FunctionCall2Coll(&subkey->sk_func,
|
||||
subkey->sk_collation,
|
||||
|
@ -2668,12 +2668,6 @@ alter_table_cmd:
|
||||
c->alterDeferrability = true;
|
||||
if ($4 & CAS_NO_INHERIT)
|
||||
c->alterInheritability = true;
|
||||
/* handle unsupported case with specific error message */
|
||||
if ($4 & CAS_NOT_VALID)
|
||||
ereport(ERROR,
|
||||
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("constraints cannot be altered to be NOT VALID"),
|
||||
parser_errposition(@4));
|
||||
processCASbits($4, @4, "FOREIGN KEY",
|
||||
&c->deferrable,
|
||||
&c->initdeferred,
|
||||
|
@ -16,22 +16,6 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
|
||||
$primary->init(allows_streaming => 1);
|
||||
$primary->start;
|
||||
|
||||
# Create file with some random data and an arbitrary size, useful to check
|
||||
# the solidity of the compression and decompression logic. The size of the
|
||||
# file is chosen to be around 640kB. This has proven to be large enough to
|
||||
# detect some issues related to LZ4, and low enough to not impact the runtime
|
||||
# of the test significantly.
|
||||
my $junk_data = $primary->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT string_agg(encode(sha256(i::bytea), 'hex'), '')
|
||||
FROM generate_series(1, 10240) s(i);));
|
||||
my $data_dir = $primary->data_dir;
|
||||
my $junk_file = "$data_dir/junk";
|
||||
open my $jf, '>', $junk_file
|
||||
or die "Could not create junk file: $!";
|
||||
print $jf $junk_data;
|
||||
close $jf;
|
||||
|
||||
# Create a tablespace directory.
|
||||
my $source_ts_path = PostgreSQL::Test::Utils::tempdir_short();
|
||||
|
||||
@ -68,12 +52,6 @@ my @test_configuration = (
|
||||
'backup_archive' => [ 'base.tar.lz4', "$tsoid.tar.lz4" ],
|
||||
'enabled' => check_pg_config("#define USE_LZ4 1")
|
||||
},
|
||||
{
|
||||
'compression_method' => 'lz4',
|
||||
'backup_flags' => [ '--compress', 'server-lz4:5' ],
|
||||
'backup_archive' => [ 'base.tar.lz4', "$tsoid.tar.lz4" ],
|
||||
'enabled' => check_pg_config("#define USE_LZ4 1")
|
||||
},
|
||||
{
|
||||
'compression_method' => 'zstd',
|
||||
'backup_flags' => [ '--compress', 'server-zstd' ],
|
||||
|
@ -15,22 +15,6 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
|
||||
$primary->init(allows_streaming => 1);
|
||||
$primary->start;
|
||||
|
||||
# Create file with some random data and an arbitrary size, useful to check
|
||||
# the solidity of the compression and decompression logic. The size of the
|
||||
# file is chosen to be around 640kB. This has proven to be large enough to
|
||||
# detect some issues related to LZ4, and low enough to not impact the runtime
|
||||
# of the test significantly.
|
||||
my $junk_data = $primary->safe_psql(
|
||||
'postgres', qq(
|
||||
SELECT string_agg(encode(sha256(i::bytea), 'hex'), '')
|
||||
FROM generate_series(1, 10240) s(i);));
|
||||
my $data_dir = $primary->data_dir;
|
||||
my $junk_file = "$data_dir/junk";
|
||||
open my $jf, '>', $junk_file
|
||||
or die "Could not create junk file: $!";
|
||||
print $jf $junk_data;
|
||||
close $jf;
|
||||
|
||||
my $backup_path = $primary->backup_dir . '/client-backup';
|
||||
my $extract_path = $primary->backup_dir . '/extracted-backup';
|
||||
|
||||
@ -53,12 +37,6 @@ my @test_configuration = (
|
||||
'backup_archive' => 'base.tar.lz4',
|
||||
'enabled' => check_pg_config("#define USE_LZ4 1")
|
||||
},
|
||||
{
|
||||
'compression_method' => 'lz4',
|
||||
'backup_flags' => [ '--compress', 'client-lz4:1' ],
|
||||
'backup_archive' => 'base.tar.lz4',
|
||||
'enabled' => check_pg_config("#define USE_LZ4 1")
|
||||
},
|
||||
{
|
||||
'compression_method' => 'zstd',
|
||||
'backup_flags' => [ '--compress', 'client-zstd:5' ],
|
||||
|
@ -322,9 +322,9 @@ astreamer_lz4_decompressor_content(astreamer *streamer,
|
||||
|
||||
mystreamer = (astreamer_lz4_frame *) streamer;
|
||||
next_in = (uint8 *) data;
|
||||
next_out = (uint8 *) mystreamer->base.bbs_buffer.data + mystreamer->bytes_written;
|
||||
next_out = (uint8 *) mystreamer->base.bbs_buffer.data;
|
||||
avail_in = len;
|
||||
avail_out = mystreamer->base.bbs_buffer.maxlen - mystreamer->bytes_written;
|
||||
avail_out = mystreamer->base.bbs_buffer.maxlen;
|
||||
|
||||
while (avail_in > 0)
|
||||
{
|
||||
|
@ -72,7 +72,7 @@ pg_comp_crc32c_dispatch(pg_crc32c crc, const void *data, size_t len)
|
||||
{
|
||||
if (__builtin_constant_p(len) && len < 32)
|
||||
{
|
||||
const unsigned char *p = (const unsigned char *) data;
|
||||
const unsigned char *p = data;
|
||||
|
||||
/*
|
||||
* For small constant inputs, inline the computation to avoid a
|
||||
|
@ -137,7 +137,6 @@ PQcancelCreate(PGconn *conn)
|
||||
goto oom_error;
|
||||
|
||||
originalHost = conn->connhost[conn->whichhost];
|
||||
cancelConn->connhost[0].type = originalHost.type;
|
||||
if (originalHost.host)
|
||||
{
|
||||
cancelConn->connhost[0].host = strdup(originalHost.host);
|
||||
|
@ -195,123 +195,54 @@ ORDER BY proname DESC, proargtypes DESC, pronamespace DESC LIMIT 1;
|
||||
(1 row)
|
||||
|
||||
--
|
||||
-- Forwards scan RowCompare qual whose row arg has a NULL that affects our
|
||||
-- initial positioning strategy
|
||||
-- Add coverage for RowCompare quals whose rhs row has a NULL that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) >= ('abs', NULL) AND proname <= 'abs'
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
|
||||
Index Cond: ((ROW(proname, proargtypes) >= ROW('abs'::name, NULL::oidvector)) AND (proname <= 'abs'::name))
|
||||
Index Cond: ((ROW(proname, proargtypes) < ROW('abs'::name, NULL::oidvector)) AND (proname = 'abs'::name))
|
||||
(2 rows)
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) >= ('abs', NULL) AND proname <= 'abs'
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
proname | proargtypes | pronamespace
|
||||
---------+-------------+--------------
|
||||
(0 rows)
|
||||
|
||||
--
|
||||
-- Forwards scan RowCompare quals whose row arg has a NULL that ends scan
|
||||
-- Add coverage for backwards scan RowCompare quals whose rhs row has a NULL
|
||||
-- that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
|
||||
Index Cond: ((proname >= 'abs'::name) AND (ROW(proname, proargtypes) < ROW('abs'::name, NULL::oidvector)))
|
||||
(2 rows)
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
proname | proargtypes | pronamespace
|
||||
---------+-------------+--------------
|
||||
(0 rows)
|
||||
|
||||
--
|
||||
-- Backwards scan RowCompare qual whose row arg has a NULL that affects our
|
||||
-- initial positioning strategy
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) <= ('abs', NULL)
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) > ('abs', NULL)
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------------------------------------------
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
Index Only Scan Backward using pg_proc_proname_args_nsp_index on pg_proc
|
||||
Index Cond: ((proname >= 'abs'::name) AND (ROW(proname, proargtypes) <= ROW('abs'::name, NULL::oidvector)))
|
||||
Index Cond: ((ROW(proname, proargtypes) > ROW('abs'::name, NULL::oidvector)) AND (proname = 'abs'::name))
|
||||
(2 rows)
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) <= ('abs', NULL)
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) > ('abs', NULL)
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
proname | proargtypes | pronamespace
|
||||
---------+-------------+--------------
|
||||
(0 rows)
|
||||
|
||||
--
|
||||
-- Backwards scan RowCompare qual whose row arg has a NULL that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) > ('abs', NULL) AND proname <= 'abs'
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
Index Only Scan Backward using pg_proc_proname_args_nsp_index on pg_proc
|
||||
Index Cond: ((ROW(proname, proargtypes) > ROW('abs'::name, NULL::oidvector)) AND (proname <= 'abs'::name))
|
||||
(2 rows)
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) > ('abs', NULL) AND proname <= 'abs'
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
proname | proargtypes | pronamespace
|
||||
---------+-------------+--------------
|
||||
(0 rows)
|
||||
|
||||
-- Makes B-Tree preprocessing deal with unmarking redundant keys that were
|
||||
-- initially marked required (test case relies on current row compare
|
||||
-- preprocessing limitations)
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname = 'zzzzzz' AND (proname, proargtypes) > ('abs', NULL)
|
||||
AND pronamespace IN (1, 2, 3) AND proargtypes IN ('26 23', '5077')
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc
|
||||
Index Cond: ((ROW(proname, proargtypes) > ROW('abs'::name, NULL::oidvector)) AND (proname = 'zzzzzz'::name) AND (proargtypes = ANY ('{"26 23",5077}'::oidvector[])) AND (pronamespace = ANY ('{1,2,3}'::oid[])))
|
||||
(2 rows)
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname = 'zzzzzz' AND (proname, proargtypes) > ('abs', NULL)
|
||||
AND pronamespace IN (1, 2, 3) AND proargtypes IN ('26 23', '5077')
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
proname | proargtypes | pronamespace
|
||||
---------+-------------+--------------
|
||||
(0 rows)
|
||||
|
||||
--
|
||||
-- Performs a recheck of > key following array advancement on previous (left
|
||||
-- sibling) page that used a high key whose attribute value corresponding to
|
||||
-- the > key was -inf (due to being truncated when the high key was created).
|
||||
-- Add coverage for recheck of > key following array advancement on previous
|
||||
-- (left sibling) page that used a high key whose attribute value corresponding
|
||||
-- to the > key was -inf (due to being truncated when the high key was created).
|
||||
--
|
||||
-- XXX This relies on the assumption that tenk1_thous_tenthous has a truncated
|
||||
-- high key "(183, -inf)" on the first page that we'll scan. The test will only
|
||||
|
@ -748,11 +748,6 @@ ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
|
||||
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
|
||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
|
||||
ERROR: cannot alter enforceability of constraint "unique_tbl_i_key" of relation "unique_tbl"
|
||||
-- can't make an existing constraint NOT VALID
|
||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT VALID;
|
||||
ERROR: constraints cannot be altered to be NOT VALID
|
||||
LINE 1: ...ABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT VALID;
|
||||
^
|
||||
DROP TABLE unique_tbl;
|
||||
--
|
||||
-- EXCLUDE constraints
|
||||
|
@ -1359,7 +1359,7 @@ LINE 1: ...e ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE INITIALLY ...
|
||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NO INHERIT;
|
||||
ERROR: constraint "fktable_fk_fkey" of relation "fktable" is not a not-null constraint
|
||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
|
||||
ERROR: constraints cannot be altered to be NOT VALID
|
||||
ERROR: FOREIGN KEY constraints cannot be marked NOT VALID
|
||||
LINE 1: ...ER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT VALID;
|
||||
^
|
||||
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey ENFORCED NOT ENFORCED;
|
||||
|
@ -143,83 +143,38 @@ SELECT proname, proargtypes, pronamespace
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC LIMIT 1;
|
||||
|
||||
--
|
||||
-- Forwards scan RowCompare qual whose row arg has a NULL that affects our
|
||||
-- initial positioning strategy
|
||||
-- Add coverage for RowCompare quals whose rhs row has a NULL that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) >= ('abs', NULL) AND proname <= 'abs'
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) >= ('abs', NULL) AND proname <= 'abs'
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
--
|
||||
-- Forwards scan RowCompare quals whose row arg has a NULL that ends scan
|
||||
-- Add coverage for backwards scan RowCompare quals whose rhs row has a NULL
|
||||
-- that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) < ('abs', NULL)
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
--
|
||||
-- Backwards scan RowCompare qual whose row arg has a NULL that affects our
|
||||
-- initial positioning strategy
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) <= ('abs', NULL)
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) > ('abs', NULL)
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname >= 'abs' AND (proname, proargtypes) <= ('abs', NULL)
|
||||
WHERE proname = 'abs' AND (proname, proargtypes) > ('abs', NULL)
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
|
||||
--
|
||||
-- Backwards scan RowCompare qual whose row arg has a NULL that ends scan
|
||||
--
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) > ('abs', NULL) AND proname <= 'abs'
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE (proname, proargtypes) > ('abs', NULL) AND proname <= 'abs'
|
||||
ORDER BY proname DESC, proargtypes DESC, pronamespace DESC;
|
||||
|
||||
-- Makes B-Tree preprocessing deal with unmarking redundant keys that were
|
||||
-- initially marked required (test case relies on current row compare
|
||||
-- preprocessing limitations)
|
||||
explain (costs off)
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname = 'zzzzzz' AND (proname, proargtypes) > ('abs', NULL)
|
||||
AND pronamespace IN (1, 2, 3) AND proargtypes IN ('26 23', '5077')
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
SELECT proname, proargtypes, pronamespace
|
||||
FROM pg_proc
|
||||
WHERE proname = 'zzzzzz' AND (proname, proargtypes) > ('abs', NULL)
|
||||
AND pronamespace IN (1, 2, 3) AND proargtypes IN ('26 23', '5077')
|
||||
ORDER BY proname, proargtypes, pronamespace;
|
||||
|
||||
--
|
||||
-- Performs a recheck of > key following array advancement on previous (left
|
||||
-- sibling) page that used a high key whose attribute value corresponding to
|
||||
-- the > key was -inf (due to being truncated when the high key was created).
|
||||
-- Add coverage for recheck of > key following array advancement on previous
|
||||
-- (left sibling) page that used a high key whose attribute value corresponding
|
||||
-- to the > key was -inf (due to being truncated when the high key was created).
|
||||
--
|
||||
-- XXX This relies on the assumption that tenk1_thous_tenthous has a truncated
|
||||
-- high key "(183, -inf)" on the first page that we'll scan. The test will only
|
||||
|
@ -537,9 +537,6 @@ CREATE TABLE UNIQUE_NOTEN_TBL(i int UNIQUE NOT ENFORCED);
|
||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key ENFORCED;
|
||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT ENFORCED;
|
||||
|
||||
-- can't make an existing constraint NOT VALID
|
||||
ALTER TABLE unique_tbl ALTER CONSTRAINT unique_tbl_i_key NOT VALID;
|
||||
|
||||
DROP TABLE unique_tbl;
|
||||
|
||||
--
|
||||
|
Loading…
x
Reference in New Issue
Block a user