Enhance nbtree ScalarArrayOp execution.

Commit 9e8da0f7 taught nbtree to handle ScalarArrayOpExpr quals
natively.  This works by pushing down the full context (the array keys)
to the nbtree index AM, enabling it to execute multiple primitive index
scans that the planner treats as one continuous index scan/index path.
This earlier enhancement enabled nbtree ScalarArrayOp index-only scans.
It also allowed scans with ScalarArrayOp quals to return ordered results
(with some notable restrictions, described further down).

Take this general approach a lot further: teach nbtree SAOP index scans
to decide how to execute ScalarArrayOp scans (when and where to start
the next primitive index scan) based on physical index characteristics.
This can be far more efficient.  All SAOP scans will now reliably avoid
duplicative leaf page accesses (just like any other nbtree index scan).
SAOP scans whose array keys are naturally clustered together now require
far fewer index descents, since we'll reliably avoid starting a new
primitive scan just to get to a later offset from the same leaf page.

The scan's arrays now advance using binary searches for the array
element that best matches the next tuple's attribute value.  Required
scan key arrays (i.e. arrays from scan keys that can terminate the scan)
ratchet forward in lockstep with the index scan.  Non-required arrays
(i.e. arrays from scan keys that can only exclude non-matching tuples)
"advance" without the process ever rolling over to a higher-order array.

Naturally, only required SAOP scan keys trigger skipping over leaf pages
(non-required arrays cannot safely end or start primitive index scans).
Consequently, even index scans of a composite index with a high-order
inequality scan key (which we'll mark required) and a low-order SAOP
scan key (which we won't mark required) now avoid repeating leaf page
accesses -- that benefit isn't limited to simpler equality-only cases.
In general, all nbtree index scans now output tuples as if they were one
continuous index scan -- even scans that mix a high-order inequality
with lower-order SAOP equalities reliably output tuples in index order.
This allows us to remove a couple of special cases that were applied
when building index paths with SAOP clauses during planning.

Bugfix commit 807a40c5 taught the planner to avoid generating unsafe
path keys: path keys on a multicolumn index path, with a SAOP clause on
any attribute beyond the first/most significant attribute.  These cases
are now all safe, so we go back to generating path keys without regard
for the presence of SAOP clauses (just like with any other clause type).
Affected queries can now exploit scan output order in all the usual ways
(e.g., certain "ORDER BY ... LIMIT n" queries can now terminate early).

Also undo changes from follow-up bugfix commit a4523c5a, which taught
the planner to produce alternative index paths, with path keys, but
without low-order SAOP index quals (filter quals were used instead).
We'll no longer generate these alternative paths, since they can no
longer offer any meaningful advantages over standard index qual paths.
Affected queries thereby avoid all of the disadvantages that come from
using filter quals within index scan nodes.  They can avoid extra heap
page accesses from using filter quals to exclude non-matching tuples
(index quals will never have that problem).  They can also skip over
irrelevant sections of the index in more cases (though only when nbtree
determines that starting another primitive scan actually makes sense).

There is a theoretical risk that removing restrictions on SAOP index
paths from the planner will break compatibility with amcanorder-based
index AMs maintained as extensions.  Such an index AM could have the
same limitations around ordered SAOP scans as nbtree had up until now.
Adding a pro forma incompatibility item about the issue to the Postgres
17 release notes seems like a good idea.

Author: Peter Geoghegan <pg@bowt.ie>
Author: Matthias van de Meent <boekewurm+postgres@gmail.com>
Reviewed-By: Heikki Linnakangas <hlinnaka@iki.fi>
Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com>
Reviewed-By: Tomas Vondra <tomas.vondra@enterprisedb.com>
Discussion: https://postgr.es/m/CAH2-Wz=ksvN_sjcnD1+Bt-WtifRA5ok48aDYnq3pkKhxgMQpcw@mail.gmail.com
This commit is contained in:
Peter Geoghegan 2024-04-06 11:47:10 -04:00
parent ddd9e43a92
commit 5bf748b86b
22 changed files with 3470 additions and 562 deletions

View File

@ -809,7 +809,8 @@ amrestrpos (IndexScanDesc scan);
<para>
<programlisting>
Size
amestimateparallelscan (void);
amestimateparallelscan (int nkeys,
int norderbys);
</programlisting>
Estimate and return the number of bytes of dynamic shared memory which
the access method will be needed to perform a parallel scan. (This number
@ -817,6 +818,13 @@ amestimateparallelscan (void);
AM-independent data in <structname>ParallelIndexScanDescData</structname>.)
</para>
<para>
The <literal>nkeys</literal> and <literal>norderbys</literal>
parameters indicate the number of quals and ordering operators that will be
used in the scan; the same values will be passed to <function>amrescan</function>.
Note that the actual values of the scan keys aren't provided yet.
</para>
<para>
It is not necessary to implement this function for access methods which
do not support parallel scans or for which the number of additional bytes

View File

@ -4064,6 +4064,19 @@ description | Waiting for a newly initialized WAL file to reach durable storage
</para>
</note>
<note>
<para>
Queries that use certain <acronym>SQL</acronym> constructs to search for
rows matching any value out of a list or array of multiple scalar values
(see <xref linkend="functions-comparisons"/>) perform multiple
<quote>primitive</quote> index scans (up to one primitive scan per scalar
value) during query execution. Each internal primitive index scan
increments <structname>pg_stat_all_indexes</structname>.<structfield>idx_scan</structfield>,
so it's possible for the count of index scans to significantly exceed the
total number of index scan executor node executions.
</para>
</note>
</sect2>
<sect2 id="monitoring-pg-statio-all-tables-view">

View File

@ -449,13 +449,10 @@ index_restrpos(IndexScanDesc scan)
/*
* index_parallelscan_estimate - estimate shared memory for parallel scan
*
* Currently, we don't pass any information to the AM-specific estimator,
* so it can probably only return a constant. In the future, we might need
* to pass more information.
*/
Size
index_parallelscan_estimate(Relation indexRelation, Snapshot snapshot)
index_parallelscan_estimate(Relation indexRelation, int nkeys, int norderbys,
Snapshot snapshot)
{
Size nbytes;
@ -474,7 +471,8 @@ index_parallelscan_estimate(Relation indexRelation, Snapshot snapshot)
*/
if (indexRelation->rd_indam->amestimateparallelscan != NULL)
nbytes = add_size(nbytes,
indexRelation->rd_indam->amestimateparallelscan());
indexRelation->rd_indam->amestimateparallelscan(nkeys,
norderbys));
return nbytes;
}

View File

@ -40,6 +40,9 @@
/*
* BTPARALLEL_NOT_INITIALIZED indicates that the scan has not started.
*
* BTPARALLEL_NEED_PRIMSCAN indicates that some process must now seize the
* scan to advance it via another call to _bt_first.
*
* BTPARALLEL_ADVANCING indicates that some process is advancing the scan to
* a new page; others must wait.
*
@ -47,11 +50,11 @@
* to a new page; some process can start doing that.
*
* BTPARALLEL_DONE indicates that the scan is complete (including error exit).
* We reach this state once for every distinct combination of array keys.
*/
typedef enum
{
BTPARALLEL_NOT_INITIALIZED,
BTPARALLEL_NEED_PRIMSCAN,
BTPARALLEL_ADVANCING,
BTPARALLEL_IDLE,
BTPARALLEL_DONE,
@ -67,10 +70,14 @@ typedef struct BTParallelScanDescData
BTPS_State btps_pageStatus; /* indicates whether next page is
* available for scan. see above for
* possible states of parallel scan. */
int btps_arrayKeyCount; /* count indicating number of array scan
* keys processed by parallel scan */
slock_t btps_mutex; /* protects above variables */
slock_t btps_mutex; /* protects above variables, btps_arrElems */
ConditionVariable btps_cv; /* used to synchronize parallel scan */
/*
* btps_arrElems is used when scans need to schedule another primitive
* index scan. Holds BTArrayKeyInfo.cur_elem offsets for scan keys.
*/
int btps_arrElems[FLEXIBLE_ARRAY_MEMBER];
} BTParallelScanDescData;
typedef struct BTParallelScanDescData *BTParallelScanDesc;
@ -204,21 +211,7 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
/* btree indexes are never lossy */
scan->xs_recheck = false;
/*
* If we have any array keys, initialize them during first call for a
* scan. We can't do this in btrescan because we don't know the scan
* direction at that time.
*/
if (so->numArrayKeys && !BTScanPosIsValid(so->currPos))
{
/* punt if we have any unsatisfiable array keys */
if (so->numArrayKeys < 0)
return false;
_bt_start_array_keys(scan, dir);
}
/* This loop handles advancing to the next array elements, if any */
/* Each loop iteration performs another primitive index scan */
do
{
/*
@ -260,8 +253,8 @@ btgettuple(IndexScanDesc scan, ScanDirection dir)
/* If we have a tuple, return it ... */
if (res)
break;
/* ... otherwise see if we have more array keys to deal with */
} while (so->numArrayKeys && _bt_advance_array_keys(scan, dir));
/* ... otherwise see if we need another primitive index scan */
} while (so->numArrayKeys && _bt_start_prim_scan(scan, dir));
return res;
}
@ -276,19 +269,7 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
int64 ntids = 0;
ItemPointer heapTid;
/*
* If we have any array keys, initialize them.
*/
if (so->numArrayKeys)
{
/* punt if we have any unsatisfiable array keys */
if (so->numArrayKeys < 0)
return ntids;
_bt_start_array_keys(scan, ForwardScanDirection);
}
/* This loop handles advancing to the next array elements, if any */
/* Each loop iteration performs another primitive index scan */
do
{
/* Fetch the first page & tuple */
@ -318,8 +299,8 @@ btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
ntids++;
}
}
/* Now see if we have more array keys to deal with */
} while (so->numArrayKeys && _bt_advance_array_keys(scan, ForwardScanDirection));
/* Now see if we need another primitive index scan */
} while (so->numArrayKeys && _bt_start_prim_scan(scan, ForwardScanDirection));
return ntids;
}
@ -348,10 +329,10 @@ btbeginscan(Relation rel, int nkeys, int norderbys)
else
so->keyData = NULL;
so->arrayKeyData = NULL; /* assume no array keys for now */
so->arraysStarted = false;
so->numArrayKeys = 0;
so->needPrimScan = false;
so->scanBehind = false;
so->arrayKeys = NULL;
so->orderProcs = NULL;
so->arrayContext = NULL;
so->killedItems = NULL; /* until needed */
@ -391,7 +372,8 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
}
so->markItemIndex = -1;
so->arrayKeyCount = 0;
so->needPrimScan = false;
so->scanBehind = false;
BTScanPosUnpinIfPinned(so->markPos);
BTScanPosInvalidate(so->markPos);
@ -425,9 +407,7 @@ btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
scankey,
scan->numberOfKeys * sizeof(ScanKeyData));
so->numberOfKeys = 0; /* until _bt_preprocess_keys sets it */
/* If any keys are SK_SEARCHARRAY type, set up array-key info */
_bt_preprocess_array_keys(scan);
so->numArrayKeys = 0; /* ditto */
}
/*
@ -455,7 +435,7 @@ btendscan(IndexScanDesc scan)
/* Release storage */
if (so->keyData != NULL)
pfree(so->keyData);
/* so->arrayKeyData and so->arrayKeys are in arrayContext */
/* so->arrayKeys and so->orderProcs are in arrayContext */
if (so->arrayContext != NULL)
MemoryContextDelete(so->arrayContext);
if (so->killedItems != NULL)
@ -490,10 +470,6 @@ btmarkpos(IndexScanDesc scan)
BTScanPosInvalidate(so->markPos);
so->markItemIndex = -1;
}
/* Also record the current positions of any array keys */
if (so->numArrayKeys)
_bt_mark_array_keys(scan);
}
/*
@ -504,10 +480,6 @@ btrestrpos(IndexScanDesc scan)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
/* Restore the marked positions of any array keys */
if (so->numArrayKeys)
_bt_restore_array_keys(scan);
if (so->markItemIndex >= 0)
{
/*
@ -546,6 +518,12 @@ btrestrpos(IndexScanDesc scan)
if (so->currTuples)
memcpy(so->currTuples, so->markTuples,
so->markPos.nextTupleOffset);
/* Reset the scan's array keys (see _bt_steppage for why) */
if (so->numArrayKeys)
{
_bt_start_array_keys(scan, so->currPos.dir);
so->needPrimScan = false;
}
}
else
BTScanPosInvalidate(so->currPos);
@ -556,9 +534,10 @@ btrestrpos(IndexScanDesc scan)
* btestimateparallelscan -- estimate storage for BTParallelScanDescData
*/
Size
btestimateparallelscan(void)
btestimateparallelscan(int nkeys, int norderbys)
{
return sizeof(BTParallelScanDescData);
/* Pessimistically assume all input scankeys will be output with arrays */
return offsetof(BTParallelScanDescData, btps_arrElems) + sizeof(int) * nkeys;
}
/*
@ -572,7 +551,6 @@ btinitparallelscan(void *target)
SpinLockInit(&bt_target->btps_mutex);
bt_target->btps_scanPage = InvalidBlockNumber;
bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
bt_target->btps_arrayKeyCount = 0;
ConditionVariableInit(&bt_target->btps_cv);
}
@ -598,7 +576,6 @@ btparallelrescan(IndexScanDesc scan)
SpinLockAcquire(&btscan->btps_mutex);
btscan->btps_scanPage = InvalidBlockNumber;
btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
btscan->btps_arrayKeyCount = 0;
SpinLockRelease(&btscan->btps_mutex);
}
@ -608,23 +585,26 @@ btparallelrescan(IndexScanDesc scan)
* or _bt_parallel_done().
*
* The return value is true if we successfully seized the scan and false
* if we did not. The latter case occurs if no pages remain for the current
* set of scankeys.
* if we did not. The latter case occurs if no pages remain.
*
* If the return value is true, *pageno returns the next or current page
* of the scan (depending on the scan direction). An invalid block number
* means the scan hasn't yet started, and P_NONE means we've reached the end.
* means the scan hasn't yet started, or that caller needs to start the next
* primitive index scan (if it's the latter case we'll set so.needPrimScan).
* The first time a participating process reaches the last page, it will return
* true and set *pageno to P_NONE; after that, further attempts to seize the
* scan will return false.
*
* Callers should ignore the value of pageno if the return value is false.
*
* Callers that are in a position to start a new primitive index scan must
* pass first=true (all other callers pass first=false). We just return false
* for first=false callers that require another primitive index scan.
*/
bool
_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
_bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno, bool first)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
BTPS_State pageStatus;
bool exit_loop = false;
bool status = true;
ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
@ -632,28 +612,69 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
*pageno = P_NONE;
if (first)
{
/*
* Initialize array related state when called from _bt_first, assuming
* that this will either be the first primitive index scan for the
* scan, or a previous explicitly scheduled primitive scan.
*
* Note: so->needPrimScan is only set when a scheduled primitive index
* scan is set to be performed in caller's worker process. It should
* not be set here by us for the first primitive scan, nor should we
* ever set it for a parallel scan that has no array keys.
*/
so->needPrimScan = false;
so->scanBehind = false;
}
else
{
/*
* Don't attempt to seize the scan when backend requires another
* primitive index scan unless we're in a position to start it now
*/
if (so->needPrimScan)
return false;
}
btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
parallel_scan->ps_offset);
while (1)
{
SpinLockAcquire(&btscan->btps_mutex);
pageStatus = btscan->btps_pageStatus;
if (so->arrayKeyCount < btscan->btps_arrayKeyCount)
if (btscan->btps_pageStatus == BTPARALLEL_DONE)
{
/* Parallel scan has already advanced to a new set of scankeys. */
/* We're done with this parallel index scan */
status = false;
}
else if (pageStatus == BTPARALLEL_DONE)
else if (btscan->btps_pageStatus == BTPARALLEL_NEED_PRIMSCAN)
{
Assert(so->numArrayKeys);
/*
* We're done with this set of scankeys. This may be the end, or
* there could be more sets to try.
* If we can start another primitive scan right away, do so.
* Otherwise just wait.
*/
status = false;
if (first)
{
btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
for (int i = 0; i < so->numArrayKeys; i++)
{
BTArrayKeyInfo *array = &so->arrayKeys[i];
ScanKey skey = &so->keyData[array->scan_key];
array->cur_elem = btscan->btps_arrElems[i];
skey->sk_argument = array->elem_values[array->cur_elem];
}
so->needPrimScan = true;
so->scanBehind = false;
*pageno = InvalidBlockNumber;
exit_loop = true;
}
}
else if (pageStatus != BTPARALLEL_ADVANCING)
else if (btscan->btps_pageStatus != BTPARALLEL_ADVANCING)
{
/*
* We have successfully seized control of the scan for the purpose
@ -677,6 +698,12 @@ _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno)
* _bt_parallel_release() -- Complete the process of advancing the scan to a
* new page. We now have the new value btps_scanPage; some other backend
* can now begin advancing the scan.
*
* Callers whose scan uses array keys must save their scan_page argument so
* that it can be passed to _bt_parallel_primscan_schedule, should caller
* determine that another primitive index scan is required. If that happens,
* scan_page won't be scanned by any backend (unless the next primitive index
* scan lands on scan_page).
*/
void
_bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
@ -704,7 +731,6 @@ _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page)
void
_bt_parallel_done(IndexScanDesc scan)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
BTParallelScanDesc btscan;
bool status_changed = false;
@ -717,13 +743,11 @@ _bt_parallel_done(IndexScanDesc scan)
parallel_scan->ps_offset);
/*
* Mark the parallel scan as done for this combination of scan keys,
* unless some other process already did so. See also
* _bt_advance_array_keys.
* Mark the parallel scan as done, unless some other process did so
* already
*/
SpinLockAcquire(&btscan->btps_mutex);
if (so->arrayKeyCount >= btscan->btps_arrayKeyCount &&
btscan->btps_pageStatus != BTPARALLEL_DONE)
if (btscan->btps_pageStatus != BTPARALLEL_DONE)
{
btscan->btps_pageStatus = BTPARALLEL_DONE;
status_changed = true;
@ -736,29 +760,39 @@ _bt_parallel_done(IndexScanDesc scan)
}
/*
* _bt_parallel_advance_array_keys() -- Advances the parallel scan for array
* keys.
* _bt_parallel_primscan_schedule() -- Schedule another primitive index scan.
*
* Updates the count of array keys processed for both local and parallel
* scans.
* Caller passes the block number most recently passed to _bt_parallel_release
* by its backend. Caller successfully schedules the next primitive index scan
* if the shared parallel state hasn't been seized since caller's backend last
* advanced the scan.
*/
void
_bt_parallel_advance_array_keys(IndexScanDesc scan)
_bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber prev_scan_page)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
BTParallelScanDesc btscan;
Assert(so->numArrayKeys);
btscan = (BTParallelScanDesc) OffsetToPointer((void *) parallel_scan,
parallel_scan->ps_offset);
so->arrayKeyCount++;
SpinLockAcquire(&btscan->btps_mutex);
if (btscan->btps_pageStatus == BTPARALLEL_DONE)
if (btscan->btps_scanPage == prev_scan_page &&
btscan->btps_pageStatus == BTPARALLEL_IDLE)
{
btscan->btps_scanPage = InvalidBlockNumber;
btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
btscan->btps_arrayKeyCount++;
btscan->btps_pageStatus = BTPARALLEL_NEED_PRIMSCAN;
/* Serialize scan's current array keys */
for (int i = 0; i < so->numArrayKeys; i++)
{
BTArrayKeyInfo *array = &so->arrayKeys[i];
btscan->btps_arrElems[i] = array->cur_elem;
}
}
SpinLockRelease(&btscan->btps_mutex);
}

View File

@ -907,7 +907,6 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
*/
if (!so->qual_ok)
{
/* Notify any other workers that we're done with this scan key. */
_bt_parallel_done(scan);
return false;
}
@ -917,10 +916,22 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
* scan has not started, proceed to find out first leaf page in the usual
* way while keeping other participating processes waiting. If the scan
* has already begun, use the page number from the shared structure.
*
* When a parallel scan has another primitive index scan scheduled, a
* parallel worker will seize the scan for that purpose now. This is
* similar to the case where the top-level scan hasn't started.
*/
if (scan->parallel_scan != NULL)
{
status = _bt_parallel_seize(scan, &blkno);
status = _bt_parallel_seize(scan, &blkno, true);
/*
* Initialize arrays (when _bt_parallel_seize didn't already set up
* the next primitive index scan)
*/
if (so->numArrayKeys && !so->needPrimScan)
_bt_start_array_keys(scan, dir);
if (!status)
return false;
else if (blkno == P_NONE)
@ -935,6 +946,16 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
goto readcomplete;
}
}
else if (so->numArrayKeys && !so->needPrimScan)
{
/*
* First _bt_first call (for current btrescan) without parallelism.
*
* Initialize arrays, and the corresponding scan keys that were just
* output by _bt_preprocess_keys.
*/
_bt_start_array_keys(scan, dir);
}
/*----------
* Examine the scan keys to discover where we need to start the scan.
@ -980,6 +1001,18 @@ _bt_first(IndexScanDesc scan, ScanDirection dir)
*
* 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.
*----------
*/
strat_total = BTEqualStrategyNumber;
@ -1502,7 +1535,8 @@ _bt_next(IndexScanDesc scan, ScanDirection dir)
* We scan the current page starting at offnum and moving in the indicated
* direction. All items matching the scan keys are loaded into currPos.items.
* moreLeft or moreRight (as appropriate) is cleared if _bt_checkkeys reports
* that there can be no more matching tuples in the current scan direction.
* that there can be no more matching tuples in the current scan direction
* (could just be for the current primitive index scan when scan has arrays).
*
* _bt_first caller passes us an offnum returned by _bt_binsrch, which might
* be an out of bounds offnum such as "maxoff + 1" in certain corner cases.
@ -1527,11 +1561,10 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
BTPageOpaque opaque;
OffsetNumber minoff;
OffsetNumber maxoff;
int itemIndex;
bool continuescan;
int indnatts;
bool continuescanPrechecked;
bool haveFirstMatch = false;
BTReadPageState pstate;
bool arrayKeys;
int itemIndex,
indnatts;
/*
* We must have the buffer pinned and locked, but the usual macro can't be
@ -1546,16 +1579,32 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
if (scan->parallel_scan)
{
if (ScanDirectionIsForward(dir))
_bt_parallel_release(scan, opaque->btpo_next);
pstate.prev_scan_page = opaque->btpo_next;
else
_bt_parallel_release(scan, BufferGetBlockNumber(so->currPos.buf));
pstate.prev_scan_page = BufferGetBlockNumber(so->currPos.buf);
_bt_parallel_release(scan, pstate.prev_scan_page);
}
continuescan = true; /* default assumption */
indnatts = IndexRelationGetNumberOfAttributes(scan->indexRelation);
arrayKeys = so->numArrayKeys != 0;
minoff = P_FIRSTDATAKEY(opaque);
maxoff = PageGetMaxOffsetNumber(page);
/* initialize page-level state that we'll pass to _bt_checkkeys */
pstate.dir = dir;
pstate.minoff = minoff;
pstate.maxoff = maxoff;
pstate.finaltup = NULL;
pstate.page = page;
pstate.offnum = InvalidOffsetNumber;
pstate.skip = InvalidOffsetNumber;
pstate.continuescan = true; /* default assumption */
pstate.prechecked = false;
pstate.firstmatch = false;
pstate.rechecks = 0;
pstate.targetdistance = 0;
/*
* We note the buffer's block number so that we can release the pin later.
* This allows us to re-read the buffer if it is needed again for hinting.
@ -1598,10 +1647,34 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
* corresponding value from the last item on the page. So checking with
* the last item on the page would give a more precise answer.
*
* We skip this for the first page in the scan to evade the possible
* slowdown of the point queries.
* We skip this for the first page read by each (primitive) scan, to avoid
* slowing down point queries. They typically don't stand to gain much
* when the optimization can be applied, and are more likely to notice the
* overhead of the precheck.
*
* The optimization is unsafe and must be avoided whenever _bt_checkkeys
* just set a low-order required array's key to the best available match
* for a truncated -inf attribute value from the prior page's high key
* (array element 0 is always the best available match in this scenario).
* It's quite likely that matches for array element 0 begin on this page,
* but the start of matches won't necessarily align with page boundaries.
* When the start of matches is somewhere in the middle of this page, it
* would be wrong to treat page's final non-pivot tuple as representative.
* Doing so might lead us to treat some of the page's earlier tuples as
* being part of a group of tuples thought to satisfy the required keys.
*
* Note: Conversely, in the case where the scan's arrays just advanced
* using the prior page's HIKEY _without_ advancement setting scanBehind,
* the start of matches must be aligned with page boundaries, which makes
* it safe to attempt the optimization here now. It's also safe when the
* prior page's HIKEY simply didn't need to advance any required array. In
* both cases we can safely assume that the _first_ tuple from this page
* must be >= the current set of array keys/equality constraints. And so
* if the final tuple is == those same keys (and also satisfies any
* required < or <= strategy scan keys) during the precheck, we can safely
* assume that this must also be true of all earlier tuples from the page.
*/
if (!firstPage && minoff < maxoff)
if (!firstPage && !so->scanBehind && minoff < maxoff)
{
ItemId iid;
IndexTuple itup;
@ -1609,22 +1682,22 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
iid = PageGetItemId(page, ScanDirectionIsForward(dir) ? maxoff : minoff);
itup = (IndexTuple) PageGetItem(page, iid);
/*
* Do the precheck. Note that we pass the pointer to the
* 'continuescanPrechecked' to the 'continuescan' argument. That will
* set flag to true if all required keys are satisfied and false
* otherwise.
*/
(void) _bt_checkkeys(scan, itup, indnatts, dir,
&continuescanPrechecked, false, false);
}
else
{
continuescanPrechecked = false;
/* Call with arrayKeys=false to avoid undesirable side-effects */
_bt_checkkeys(scan, &pstate, false, itup, indnatts);
pstate.prechecked = pstate.continuescan;
pstate.continuescan = true; /* reset */
}
if (ScanDirectionIsForward(dir))
{
/* SK_SEARCHARRAY forward scans must provide high key up front */
if (arrayKeys && !P_RIGHTMOST(opaque))
{
ItemId iid = PageGetItemId(page, P_HIKEY);
pstate.finaltup = (IndexTuple) PageGetItem(page, iid);
}
/* load items[] in ascending order */
itemIndex = 0;
@ -1649,23 +1722,28 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
itup = (IndexTuple) PageGetItem(page, iid);
Assert(!BTreeTupleIsPivot(itup));
passes_quals = _bt_checkkeys(scan, itup, indnatts, dir,
&continuescan,
continuescanPrechecked,
haveFirstMatch);
pstate.offnum = offnum;
passes_quals = _bt_checkkeys(scan, &pstate, arrayKeys,
itup, indnatts);
/*
* If the result of prechecking required keys was true, then in
* assert-enabled builds we also recheck that the _bt_checkkeys()
* result is the same.
* Check if we need to skip ahead to a later tuple (only possible
* when the scan uses array keys)
*/
Assert((!continuescanPrechecked && haveFirstMatch) ||
passes_quals == _bt_checkkeys(scan, itup, indnatts, dir,
&continuescan, false, false));
if (arrayKeys && OffsetNumberIsValid(pstate.skip))
{
Assert(!passes_quals && pstate.continuescan);
Assert(offnum < pstate.skip);
offnum = pstate.skip;
pstate.skip = InvalidOffsetNumber;
continue;
}
if (passes_quals)
{
/* tuple passes all scan key conditions */
haveFirstMatch = true;
pstate.firstmatch = true;
if (!BTreeTupleIsPosting(itup))
{
/* Remember it */
@ -1696,7 +1774,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
}
}
/* When !continuescan, there can't be any more matches, so stop */
if (!continuescan)
if (!pstate.continuescan)
break;
offnum = OffsetNumberNext(offnum);
@ -1713,17 +1791,18 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
* only appear on non-pivot tuples on the right sibling page are
* common.
*/
if (continuescan && !P_RIGHTMOST(opaque))
if (pstate.continuescan && !P_RIGHTMOST(opaque))
{
ItemId iid = PageGetItemId(page, P_HIKEY);
IndexTuple itup = (IndexTuple) PageGetItem(page, iid);
int truncatt;
truncatt = BTreeTupleGetNAtts(itup, scan->indexRelation);
_bt_checkkeys(scan, itup, truncatt, dir, &continuescan, false, false);
pstate.prechecked = false; /* precheck didn't cover HIKEY */
_bt_checkkeys(scan, &pstate, arrayKeys, itup, truncatt);
}
if (!continuescan)
if (!pstate.continuescan)
so->currPos.moreRight = false;
Assert(itemIndex <= MaxTIDsPerBTreePage);
@ -1733,6 +1812,14 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
}
else
{
/* SK_SEARCHARRAY backward scans must provide final tuple up front */
if (arrayKeys && minoff <= maxoff && !P_LEFTMOST(opaque))
{
ItemId iid = PageGetItemId(page, minoff);
pstate.finaltup = (IndexTuple) PageGetItem(page, iid);
}
/* load items[] in descending order */
itemIndex = MaxTIDsPerBTreePage;
@ -1772,23 +1859,28 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
itup = (IndexTuple) PageGetItem(page, iid);
Assert(!BTreeTupleIsPivot(itup));
passes_quals = _bt_checkkeys(scan, itup, indnatts, dir,
&continuescan,
continuescanPrechecked,
haveFirstMatch);
pstate.offnum = offnum;
passes_quals = _bt_checkkeys(scan, &pstate, arrayKeys,
itup, indnatts);
/*
* If the result of prechecking required keys was true, then in
* assert-enabled builds we also recheck that the _bt_checkkeys()
* result is the same.
* Check if we need to skip ahead to a later tuple (only possible
* when the scan uses array keys)
*/
Assert((!continuescanPrechecked && !haveFirstMatch) ||
passes_quals == _bt_checkkeys(scan, itup, indnatts, dir,
&continuescan, false, false));
if (arrayKeys && OffsetNumberIsValid(pstate.skip))
{
Assert(!passes_quals && pstate.continuescan);
Assert(offnum > pstate.skip);
offnum = pstate.skip;
pstate.skip = InvalidOffsetNumber;
continue;
}
if (passes_quals && tuple_alive)
{
/* tuple passes all scan key conditions */
haveFirstMatch = true;
pstate.firstmatch = true;
if (!BTreeTupleIsPosting(itup))
{
/* Remember it */
@ -1824,7 +1916,7 @@ _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum,
}
}
}
if (!continuescan)
if (!pstate.continuescan)
{
/* there can't be any more matches, so stop */
so->currPos.moreLeft = false;
@ -1970,6 +2062,31 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
so->currPos.nextTupleOffset);
so->markPos.itemIndex = so->markItemIndex;
so->markItemIndex = -1;
/*
* If we're just about to start the next primitive index scan
* (possible with a scan that has arrays keys, and needs to skip to
* continue in the current scan direction), moreLeft/moreRight only
* indicate the end of the current primitive index scan. They must
* never be taken to indicate that the top-level index scan has ended
* (that would be wrong).
*
* We could handle this case by treating the current array keys as
* markPos state. But depending on the current array state like this
* would add complexity. Instead, we just unset markPos's copy of
* moreRight or moreLeft (whichever might be affected), while making
* btrestpos reset the scan's arrays to their initial scan positions.
* In effect, btrestpos leaves advancing the arrays up to the first
* _bt_readpage call (that takes place after it has restored markPos).
*/
Assert(so->markPos.dir == dir);
if (so->needPrimScan)
{
if (ScanDirectionIsForward(dir))
so->markPos.moreRight = true;
else
so->markPos.moreLeft = true;
}
}
if (ScanDirectionIsForward(dir))
@ -1981,7 +2098,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
* Seize the scan to get the next block number; if the scan has
* ended already, bail out.
*/
status = _bt_parallel_seize(scan, &blkno);
status = _bt_parallel_seize(scan, &blkno, false);
if (!status)
{
/* release the previous buffer, if pinned */
@ -2013,7 +2130,7 @@ _bt_steppage(IndexScanDesc scan, ScanDirection dir)
* Seize the scan to get the current block number; if the scan has
* ended already, bail out.
*/
status = _bt_parallel_seize(scan, &blkno);
status = _bt_parallel_seize(scan, &blkno, false);
BTScanPosUnpinIfPinned(so->currPos);
if (!status)
{
@ -2097,7 +2214,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
if (scan->parallel_scan != NULL)
{
_bt_relbuf(rel, so->currPos.buf);
status = _bt_parallel_seize(scan, &blkno);
status = _bt_parallel_seize(scan, &blkno, false);
if (!status)
{
BTScanPosInvalidate(so->currPos);
@ -2193,7 +2310,7 @@ _bt_readnextpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
if (scan->parallel_scan != NULL)
{
_bt_relbuf(rel, so->currPos.buf);
status = _bt_parallel_seize(scan, &blkno);
status = _bt_parallel_seize(scan, &blkno, false);
if (!status)
{
BTScanPosInvalidate(so->currPos);
@ -2218,6 +2335,8 @@ _bt_parallel_readpage(IndexScanDesc scan, BlockNumber blkno, ScanDirection dir)
{
BTScanOpaque so = (BTScanOpaque) scan->opaque;
Assert(!so->needPrimScan);
_bt_initialize_more_data(so, dir);
if (!_bt_readnextpage(scan, blkno, dir))
@ -2524,14 +2643,22 @@ _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
}
/*
* _bt_initialize_more_data() -- initialize moreLeft/moreRight appropriately
* for scan direction
* _bt_initialize_more_data() -- initialize moreLeft, moreRight and scan dir
* from currPos
*/
static inline void
_bt_initialize_more_data(BTScanOpaque so, ScanDirection dir)
{
/* initialize moreLeft/moreRight appropriately for scan direction */
if (ScanDirectionIsForward(dir))
so->currPos.dir = dir;
if (so->needPrimScan)
{
Assert(so->numArrayKeys);
so->currPos.moreLeft = true;
so->currPos.moreRight = true;
so->needPrimScan = false;
}
else if (ScanDirectionIsForward(dir))
{
so->currPos.moreLeft = false;
so->currPos.moreRight = true;

File diff suppressed because it is too large Load Diff

View File

@ -628,6 +628,8 @@ ExecIndexOnlyScanEstimate(IndexOnlyScanState *node,
EState *estate = node->ss.ps.state;
node->ioss_PscanLen = index_parallelscan_estimate(node->ioss_RelationDesc,
node->ioss_NumScanKeys,
node->ioss_NumOrderByKeys,
estate->es_snapshot);
shm_toc_estimate_chunk(&pcxt->estimator, node->ioss_PscanLen);
shm_toc_estimate_keys(&pcxt->estimator, 1);

View File

@ -1644,6 +1644,8 @@ ExecIndexScanEstimate(IndexScanState *node,
EState *estate = node->ss.ps.state;
node->iss_PscanLen = index_parallelscan_estimate(node->iss_RelationDesc,
node->iss_NumScanKeys,
node->iss_NumOrderByKeys,
estate->es_snapshot);
shm_toc_estimate_chunk(&pcxt->estimator, node->iss_PscanLen);
shm_toc_estimate_keys(&pcxt->estimator, 1);

View File

@ -106,8 +106,7 @@ static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
bool *skip_lower_saop);
bool *skip_nonnative_saop);
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
List *clauses, List *other_clauses);
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
@ -706,8 +705,6 @@ eclass_already_used(EquivalenceClass *parent_ec, Relids oldrelids,
* index AM supports them natively, we should just include them in simple
* index paths. If not, we should exclude them while building simple index
* paths, and then make a separate attempt to include them in bitmap paths.
* Furthermore, we should consider excluding lower-order ScalarArrayOpExpr
* quals so as to create ordered paths.
*/
static void
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
@ -716,37 +713,17 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
{
List *indexpaths;
bool skip_nonnative_saop = false;
bool skip_lower_saop = false;
ListCell *lc;
/*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively, and skip any such
* clauses for index columns after the first (so that we produce ordered
* paths if possible).
* clauses only if the index AM supports them natively.
*/
indexpaths = build_index_paths(root, rel,
index, clauses,
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
&skip_lower_saop);
/*
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
* that supports them, then try again including those clauses. This will
* produce paths with more selectivity but no ordering.
*/
if (skip_lower_saop)
{
indexpaths = list_concat(indexpaths,
build_index_paths(root, rel,
index, clauses,
index->predOK,
ST_ANYSCAN,
&skip_nonnative_saop,
NULL));
}
&skip_nonnative_saop);
/*
* Submit all the ones that can form plain IndexScan plans to add_path. (A
@ -784,7 +761,6 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
index, clauses,
false,
ST_BITMAPSCAN,
NULL,
NULL);
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
}
@ -817,27 +793,19 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
* to true if we found any such clauses (caller must initialize the variable
* to false). If it's NULL, we do not ignore ScalarArrayOpExpr clauses.
*
* If skip_lower_saop is non-NULL, we ignore ScalarArrayOpExpr clauses for
* non-first index columns, and we set *skip_lower_saop to true if we found
* any such clauses (caller must initialize the variable to false). If it's
* NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
* result in considering the scan's output to be unordered.
*
* 'rel' is the index's heap relation
* 'index' is the index for which we want to generate paths
* 'clauses' is the collection of indexable clauses (IndexClause nodes)
* 'useful_predicate' indicates whether the index has a useful predicate
* 'scantype' indicates whether we need plain or bitmap scan support
* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
* 'skip_lower_saop' indicates whether to accept non-first-column SAOP
*/
static List *
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexOptInfo *index, IndexClauseSet *clauses,
bool useful_predicate,
ScanTypeControl scantype,
bool *skip_nonnative_saop,
bool *skip_lower_saop)
bool *skip_nonnative_saop)
{
List *result = NIL;
IndexPath *ipath;
@ -848,12 +816,13 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
List *orderbyclausecols;
List *index_pathkeys;
List *useful_pathkeys;
bool found_lower_saop_clause;
bool pathkeys_possibly_useful;
bool index_is_ordered;
bool index_only_scan;
int indexcol;
Assert(skip_nonnative_saop != NULL || scantype == ST_BITMAPSCAN);
/*
* Check that index supports the desired scan type(s)
*/
@ -880,19 +849,11 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
* on by btree and possibly other places.) The list can be empty, if the
* index AM allows that.
*
* found_lower_saop_clause is set true if we accept a ScalarArrayOpExpr
* index clause for a non-first index column. This prevents us from
* assuming that the scan result is ordered. (Actually, the result is
* still ordered if there are equality constraints for all earlier
* columns, but it seems too expensive and non-modular for this code to be
* aware of that refinement.)
*
* We also build a Relids set showing which outer rels are required by the
* selected clauses. Any lateral_relids are included in that, but not
* otherwise accounted for.
*/
index_clauses = NIL;
found_lower_saop_clause = false;
outer_relids = bms_copy(rel->lateral_relids);
for (indexcol = 0; indexcol < index->nkeycolumns; indexcol++)
{
@ -903,30 +864,18 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
IndexClause *iclause = (IndexClause *) lfirst(lc);
RestrictInfo *rinfo = iclause->rinfo;
/* We might need to omit ScalarArrayOpExpr clauses */
if (IsA(rinfo->clause, ScalarArrayOpExpr))
if (skip_nonnative_saop && !index->amsearcharray &&
IsA(rinfo->clause, ScalarArrayOpExpr))
{
if (!index->amsearcharray)
{
if (skip_nonnative_saop)
{
/* Ignore because not supported by index */
*skip_nonnative_saop = true;
continue;
}
/* Caller had better intend this only for bitmap scan */
Assert(scantype == ST_BITMAPSCAN);
}
if (indexcol > 0)
{
if (skip_lower_saop)
{
/* Caller doesn't want to lose index ordering */
*skip_lower_saop = true;
continue;
}
found_lower_saop_clause = true;
}
/*
* Caller asked us to generate IndexPaths that omit any
* ScalarArrayOpExpr clauses when the underlying index AM
* lacks native support.
*
* We must omit this clause (and tell caller about it).
*/
*skip_nonnative_saop = true;
continue;
}
/* OK to include this clause */
@ -956,11 +905,9 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
/*
* 2. Compute pathkeys describing index's ordering, if any, then see how
* many of them are actually useful for this query. This is not relevant
* if we are only trying to build bitmap indexscans, nor if we have to
* assume the scan is unordered.
* if we are only trying to build bitmap indexscans.
*/
pathkeys_possibly_useful = (scantype != ST_BITMAPSCAN &&
!found_lower_saop_clause &&
has_useful_pathkeys(root, rel));
index_is_ordered = (index->sortopfamily != NULL);
if (index_is_ordered && pathkeys_possibly_useful)
@ -1212,7 +1159,6 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
index, &clauseset,
useful_predicate,
ST_BITMAPSCAN,
NULL,
NULL);
result = list_concat(result, indexpaths);
}

View File

@ -6572,21 +6572,26 @@ genericcostestimate(PlannerInfo *root,
selectivityQuals = add_predicate_to_index_quals(index, indexQuals);
/*
* Check for ScalarArrayOpExpr index quals, and estimate the number of
* index scans that will be performed.
* If caller didn't give us an estimate for ScalarArrayOpExpr index scans,
* just assume that the number of index descents is the number of distinct
* combinations of array elements from all of the scan's SAOP clauses.
*/
num_sa_scans = 1;
foreach(l, indexQuals)
num_sa_scans = costs->num_sa_scans;
if (num_sa_scans < 1)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (IsA(rinfo->clause, ScalarArrayOpExpr))
num_sa_scans = 1;
foreach(l, indexQuals)
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
double alength = estimate_array_length(root, lsecond(saop->args));
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (alength > 1)
num_sa_scans *= alength;
if (IsA(rinfo->clause, ScalarArrayOpExpr))
{
ScalarArrayOpExpr *saop = (ScalarArrayOpExpr *) rinfo->clause;
double alength = estimate_array_length(root, lsecond(saop->args));
if (alength > 1)
num_sa_scans *= alength;
}
}
}
@ -6813,9 +6818,9 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
* For a RowCompareExpr, we consider only the first column, just as
* rowcomparesel() does.
*
* If there's a ScalarArrayOpExpr in the quals, we'll actually perform N
* index scans not one, but the ScalarArrayOpExpr's operator can be
* considered to act the same as it normally does.
* If there's a ScalarArrayOpExpr in the quals, we'll actually perform up
* to N index descents (not just one), but the ScalarArrayOpExpr's
* operator can be considered to act the same as it normally does.
*/
indexBoundQuals = NIL;
indexcol = 0;
@ -6867,7 +6872,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
clause_op = saop->opno;
found_saop = true;
/* count number of SA scans induced by indexBoundQuals only */
/* estimate SA descents by indexBoundQuals only */
if (alength > 1)
num_sa_scans *= alength;
}
@ -6930,10 +6935,48 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
NULL);
numIndexTuples = btreeSelectivity * index->rel->tuples;
/*
* btree automatically combines individual ScalarArrayOpExpr primitive
* index scans whenever the tuples covered by the next set of array
* keys are close to tuples covered by the current set. That puts a
* natural ceiling on the worst case number of descents -- there
* cannot possibly be more than one descent per leaf page scanned.
*
* Clamp the number of descents to at most 1/3 the number of index
* pages. This avoids implausibly high estimates with low selectivity
* paths, where scans usually require only one or two descents. This
* is most likely to help when there are several SAOP clauses, where
* naively accepting the total number of distinct combinations of
* array elements as the number of descents would frequently lead to
* wild overestimates.
*
* We somewhat arbitrarily don't just make the cutoff the total number
* of leaf pages (we make it 1/3 the total number of pages instead) to
* give the btree code credit for its ability to continue on the leaf
* level with low selectivity scans.
*/
num_sa_scans = Min(num_sa_scans, ceil(index->pages * 0.3333333));
num_sa_scans = Max(num_sa_scans, 1);
/*
* As in genericcostestimate(), we have to adjust for any
* ScalarArrayOpExpr quals included in indexBoundQuals, and then round
* to integer.
*
* It is tempting to make genericcostestimate behave as if SAOP
* clauses work in almost the same way as scalar operators during
* btree scans, making the top-level scan look like a continuous scan
* (as opposed to num_sa_scans-many primitive index scans). After
* all, btree scans mostly work like that at runtime. However, such a
* scheme would badly bias genericcostestimate's simplistic appraoch
* to calculating numIndexPages through prorating.
*
* Stick with the approach taken by non-native SAOP scans for now.
* genericcostestimate will use the Mackert-Lohman formula to
* compensate for repeat page fetches, even though that definitely
* won't happen during btree scans (not for leaf pages, at least).
* We're usually very pessimistic about the number of primitive index
* scans that will be required, but it's not clear how to do better.
*/
numIndexTuples = rint(numIndexTuples / num_sa_scans);
}
@ -6942,6 +6985,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
* Now do generic index cost estimation.
*/
costs.numIndexTuples = numIndexTuples;
costs.num_sa_scans = num_sa_scans;
genericcostestimate(root, path, loop_count, &costs);
@ -6952,9 +6996,9 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
* comparisons to descend a btree of N leaf tuples. We charge one
* cpu_operator_cost per comparison.
*
* If there are ScalarArrayOpExprs, charge this once per SA scan. The
* ones after the first one are not startup cost so far as the overall
* plan is concerned, so add them only to "total" cost.
* If there are ScalarArrayOpExprs, charge this once per estimated SA
* index descent. The ones after the first one are not startup cost so
* far as the overall plan goes, so just add them to "total" cost.
*/
if (index->tuples > 1) /* avoid computing log(0) */
{
@ -6971,7 +7015,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
* in cases where only a single leaf page is expected to be visited. This
* cost is somewhat arbitrarily set at 50x cpu_operator_cost per page
* touched. The number of such pages is btree tree height plus one (ie,
* we charge for the leaf page too). As above, charge once per SA scan.
* we charge for the leaf page too). As above, charge once per estimated
* SA index descent.
*/
descentCost = (index->tree_height + 1) * DEFAULT_PAGE_CPU_MULTIPLIER * cpu_operator_cost;
costs.indexStartupCost += descentCost;

View File

@ -194,7 +194,7 @@ typedef void (*amrestrpos_function) (IndexScanDesc scan);
*/
/* estimate size of parallel scan descriptor */
typedef Size (*amestimateparallelscan_function) (void);
typedef Size (*amestimateparallelscan_function) (int nkeys, int norderbys);
/* prepare for parallel index scan */
typedef void (*aminitparallelscan_function) (void *target);

View File

@ -165,7 +165,8 @@ extern void index_rescan(IndexScanDesc scan,
extern void index_endscan(IndexScanDesc scan);
extern void index_markpos(IndexScanDesc scan);
extern void index_restrpos(IndexScanDesc scan);
extern Size index_parallelscan_estimate(Relation indexRelation, Snapshot snapshot);
extern Size index_parallelscan_estimate(Relation indexRelation,
int nkeys, int norderbys, Snapshot snapshot);
extern void index_parallelscan_initialize(Relation heapRelation,
Relation indexRelation, Snapshot snapshot,
ParallelIndexScanDesc target);

View File

@ -960,11 +960,20 @@ typedef struct BTScanPosData
* moreLeft and moreRight track whether we think there may be matching
* index entries to the left and right of the current page, respectively.
* We can clear the appropriate one of these flags when _bt_checkkeys()
* returns continuescan = false.
* sets BTReadPageState.continuescan = false.
*/
bool moreLeft;
bool moreRight;
/*
* Direction of the scan at the time that _bt_readpage was called.
*
* Used by btrestrpos to "restore" the scan's array keys by resetting each
* array to its first element's value (first in this scan direction). This
* avoids the need to directly track the array keys in btmarkpos.
*/
ScanDirection dir;
/*
* If we are doing an index-only scan, nextTupleOffset is the first free
* location in the associated tuple storage workspace.
@ -1022,9 +1031,8 @@ typedef BTScanPosData *BTScanPos;
/* We need one of these for each equality-type SK_SEARCHARRAY scan key */
typedef struct BTArrayKeyInfo
{
int scan_key; /* index of associated key in arrayKeyData */
int scan_key; /* index of associated key in keyData */
int cur_elem; /* index of current element in elem_values */
int mark_elem; /* index of marked element in elem_values */
int num_elems; /* number of elems in current array value */
Datum *elem_values; /* array of num_elems Datums */
} BTArrayKeyInfo;
@ -1037,14 +1045,11 @@ typedef struct BTScanOpaqueData
ScanKey keyData; /* array of preprocessed scan keys */
/* workspace for SK_SEARCHARRAY support */
ScanKey arrayKeyData; /* modified copy of scan->keyData */
bool arraysStarted; /* Started array keys, but have yet to "reach
* past the end" of all arrays? */
int numArrayKeys; /* number of equality-type array keys (-1 if
* there are any unsatisfiable array keys) */
int arrayKeyCount; /* count indicating number of array scan keys
* processed */
int numArrayKeys; /* number of equality-type array keys */
bool needPrimScan; /* New prim scan to continue in current dir? */
bool scanBehind; /* Last array advancement matched -inf attr? */
BTArrayKeyInfo *arrayKeys; /* info about each equality-type array key */
FmgrInfo *orderProcs; /* ORDER procs for required equality keys */
MemoryContext arrayContext; /* scan-lifespan context for array data */
/* info about killed items if any (killedItems is NULL if never used) */
@ -1075,6 +1080,42 @@ typedef struct BTScanOpaqueData
typedef BTScanOpaqueData *BTScanOpaque;
/*
* _bt_readpage state used across _bt_checkkeys calls for a page
*/
typedef struct BTReadPageState
{
/* Input parameters, set by _bt_readpage for _bt_checkkeys */
ScanDirection dir; /* current scan direction */
OffsetNumber minoff; /* Lowest non-pivot tuple's offset */
OffsetNumber maxoff; /* Highest non-pivot tuple's offset */
IndexTuple finaltup; /* Needed by scans with array keys */
BlockNumber prev_scan_page; /* previous _bt_parallel_release block */
Page page; /* Page being read */
/* Per-tuple input parameters, set by _bt_readpage for _bt_checkkeys */
OffsetNumber offnum; /* current tuple's page offset number */
/* Output parameter, set by _bt_checkkeys for _bt_readpage */
OffsetNumber skip; /* Array keys "look ahead" skip offnum */
bool continuescan; /* Terminate ongoing (primitive) index scan? */
/*
* Input and output parameters, set and unset by both _bt_readpage and
* _bt_checkkeys to manage precheck optimizations
*/
bool prechecked; /* precheck set continuescan to 'true'? */
bool firstmatch; /* at least one match so far? */
/*
* Private _bt_checkkeys state used to manage "look ahead" optimization
* (only used during scans with array keys)
*/
int16 rechecks;
int16 targetdistance;
} BTReadPageState;
/*
* We use some private sk_flags bits in preprocessed scan keys. We're allowed
* to use bits 16-31 (see skey.h). The uppermost bits are copied from the
@ -1128,7 +1169,7 @@ extern bool btinsert(Relation rel, Datum *values, bool *isnull,
bool indexUnchanged,
struct IndexInfo *indexInfo);
extern IndexScanDesc btbeginscan(Relation rel, int nkeys, int norderbys);
extern Size btestimateparallelscan(void);
extern Size btestimateparallelscan(int nkeys, int norderbys);
extern void btinitparallelscan(void *target);
extern bool btgettuple(IndexScanDesc scan, ScanDirection dir);
extern int64 btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
@ -1149,10 +1190,12 @@ extern bool btcanreturn(Relation index, int attno);
/*
* prototypes for internal functions in nbtree.c
*/
extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno);
extern bool _bt_parallel_seize(IndexScanDesc scan, BlockNumber *pageno,
bool first);
extern void _bt_parallel_release(IndexScanDesc scan, BlockNumber scan_page);
extern void _bt_parallel_done(IndexScanDesc scan);
extern void _bt_parallel_advance_array_keys(IndexScanDesc scan);
extern void _bt_parallel_primscan_schedule(IndexScanDesc scan,
BlockNumber prev_scan_page);
/*
* prototypes for functions in nbtdedup.c
@ -1243,15 +1286,11 @@ extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost);
*/
extern BTScanInsert _bt_mkscankey(Relation rel, IndexTuple itup);
extern void _bt_freestack(BTStack stack);
extern void _bt_preprocess_array_keys(IndexScanDesc scan);
extern bool _bt_start_prim_scan(IndexScanDesc scan, ScanDirection dir);
extern void _bt_start_array_keys(IndexScanDesc scan, ScanDirection dir);
extern bool _bt_advance_array_keys(IndexScanDesc scan, ScanDirection dir);
extern void _bt_mark_array_keys(IndexScanDesc scan);
extern void _bt_restore_array_keys(IndexScanDesc scan);
extern void _bt_preprocess_keys(IndexScanDesc scan);
extern bool _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple,
int tupnatts, ScanDirection dir, bool *continuescan,
bool requiredMatchedByPrecheck, bool haveFirstMatch);
extern bool _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arrayKeys,
IndexTuple tuple, int tupnatts);
extern void _bt_killitems(IndexScanDesc scan);
extern BTCycleId _bt_vacuum_cycleid(Relation rel);
extern BTCycleId _bt_start_vacuum(Relation rel);

View File

@ -117,6 +117,9 @@ typedef struct VariableStatData
* Callers should initialize all fields of GenericCosts to zero. In addition,
* they can set numIndexTuples to some positive value if they have a better
* than default way of estimating the number of leaf index tuples visited.
* Similarly, they can set num_sa_scans to some value >= 1 for an index AM
* that doesn't necessarily perform exactly one primitive index scan per
* distinct combination of ScalarArrayOp array elements.
*/
typedef struct
{

View File

@ -189,6 +189,58 @@ select hundred, twenty from tenk1 where hundred <= 48 order by hundred desc limi
48 | 8
(1 row)
--
-- Add coverage for ScalarArrayOp btree quals with pivot tuple constants
--
explain (costs off)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82);
QUERY PLAN
------------------------------------------------------------------
Unique
-> Index Only Scan using tenk1_hundred on tenk1
Index Cond: (hundred = ANY ('{47,48,72,82}'::integer[]))
(3 rows)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82);
hundred
---------
47
48
72
82
(4 rows)
explain (costs off)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc;
QUERY PLAN
------------------------------------------------------------------
Unique
-> Index Only Scan Backward using tenk1_hundred on tenk1
Index Cond: (hundred = ANY ('{47,48,72,82}'::integer[]))
(3 rows)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc;
hundred
---------
82
72
48
47
(4 rows)
explain (costs off)
select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000;
QUERY PLAN
---------------------------------------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand = ANY ('{364,366,380}'::integer[])) AND (tenthous = 200000))
(2 rows)
select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000;
thousand
----------
(0 rows)
--
-- Check correct optimization of LIKE (special index operator support)
-- for both indexscan and bitmapscan cases

View File

@ -1698,6 +1698,12 @@ SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
0
(1 row)
SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IN (-1, 0, 1);
count
-------
1
(1 row)
DROP INDEX onek_nulltest;
CREATE UNIQUE INDEX onek_nulltest ON onek_with_null (unique2 desc nulls last,unique1);
SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL;
@ -1910,7 +1916,7 @@ SELECT count(*) FROM dupindexcols
(1 row)
--
-- Check ordering of =ANY indexqual results (bug in 9.2.0)
-- Check that index scans with =ANY indexquals return rows in index order
--
explain (costs off)
SELECT unique1 FROM tenk1
@ -1932,49 +1938,186 @@ ORDER BY unique1;
42
(3 rows)
-- Non-required array scan key on "tenthous":
explain (costs off)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
QUERY PLAN
-------------------------------------------------------
QUERY PLAN
--------------------------------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (thousand < 2)
Filter: (tenthous = ANY ('{1001,3000}'::integer[]))
Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
(2 rows)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
thousand | tenthous
----------+----------
0 | 3000
1 | 1001
(2 rows)
-- Non-required array scan key on "tenthous", backward scan:
explain (costs off)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand DESC, tenthous DESC;
QUERY PLAN
--------------------------------------------------------------------------------
Index Only Scan Backward using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
(2 rows)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand DESC, tenthous DESC;
thousand | tenthous
----------+----------
1 | 1001
0 | 3000
(2 rows)
--
-- Check elimination of redundant and contradictory index quals
--
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}');
QUERY PLAN
----------------------------------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = ANY ('{7,8,9}'::integer[])))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}');
unique1
---------
7
(1 row)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]);
QUERY PLAN
----------------------------------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{7,14,22}'::integer[])) AND (unique1 = ANY ('{33,44}'::bigint[])))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]);
unique1
---------
(0 rows)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1;
QUERY PLAN
---------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = 1))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1;
unique1
---------
1
(1 row)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345;
QUERY PLAN
-------------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 = 12345))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345;
unique1
---------
(0 rows)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42;
QUERY PLAN
-----------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 >= 42))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42;
unique1
---------
42
(1 row)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42;
QUERY PLAN
----------------------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 > 42))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42;
unique1
---------
(0 rows)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999;
QUERY PLAN
--------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 > 9996) AND (unique1 >= 9999))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999;
unique1
---------
9999
(1 row)
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3;
QUERY PLAN
--------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 < 3) AND (unique1 <= 3))
(2 rows)
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3;
unique1
---------
0
1
2
(3 rows)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
thousand | tenthous
----------+----------
0 | 3000
1 | 1001
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint;
QUERY PLAN
------------------------------------------------------------
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 < 3) AND (unique1 < '-1'::bigint))
(2 rows)
SET enable_indexonlyscan = OFF;
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint;
unique1
---------
(0 rows)
explain (costs off)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint;
QUERY PLAN
--------------------------------------------------------------------------------------
Sort
Sort Key: thousand
-> Index Scan using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
(4 rows)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
thousand | tenthous
----------+----------
0 | 3000
1 | 1001
Index Only Scan using tenk1_unique1 on tenk1
Index Cond: ((unique1 = ANY ('{1,42,7}'::integer[])) AND (unique1 < '-1'::bigint))
(2 rows)
RESET enable_indexonlyscan;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint;
unique1
---------
(0 rows)
--
-- Check elimination of constant-NULL subexpressions
--

View File

@ -8880,10 +8880,9 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
Merge Cond: (j1.id1 = j2.id1)
Join Filter: (j2.id2 = j1.id2)
-> Index Scan using j1_id1_idx on j1
-> Index Only Scan using j2_pkey on j2
-> Index Scan using j2_id1_idx on j2
Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
Filter: ((id1 % 1000) = 1)
(7 rows)
(6 rows)
select * from j1
inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2

View File

@ -361,6 +361,7 @@ alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
set random_page_cost = 2;
explain (costs off)
select count((unique1)) from tenk1 where hundred > 1;
QUERY PLAN
@ -379,6 +380,30 @@ select count((unique1)) from tenk1 where hundred > 1;
9800
(1 row)
-- Parallel ScalarArrayOp index scan
explain (costs off)
select count((unique1)) from tenk1
where hundred = any ((select array_agg(i) from generate_series(1, 100, 15) i)::int[]);
QUERY PLAN
---------------------------------------------------------------------
Finalize Aggregate
InitPlan 1
-> Aggregate
-> Function Scan on generate_series i
-> Gather
Workers Planned: 4
-> Partial Aggregate
-> Parallel Index Scan using tenk1_hundred on tenk1
Index Cond: (hundred = ANY ((InitPlan 1).col1))
(9 rows)
select count((unique1)) from tenk1
where hundred = any ((select array_agg(i) from generate_series(1, 100, 15) i)::int[]);
count
-------
700
(1 row)
-- test parallel index-only scans.
explain (costs off)
select count(*) from tenk1 where thousand > 95;

View File

@ -135,6 +135,21 @@ explain (costs off)
select hundred, twenty from tenk1 where hundred <= 48 order by hundred desc limit 1;
select hundred, twenty from tenk1 where hundred <= 48 order by hundred desc limit 1;
--
-- Add coverage for ScalarArrayOp btree quals with pivot tuple constants
--
explain (costs off)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82);
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82);
explain (costs off)
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc;
select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc;
explain (costs off)
select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000;
select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000;
--
-- Check correct optimization of LIKE (special index operator support)
-- for both indexscan and bitmapscan cases

View File

@ -668,6 +668,7 @@ SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL;
SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IS NOT NULL;
SELECT count(*) FROM onek_with_null WHERE unique1 IS NOT NULL AND unique1 > 500;
SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique1 > 500;
SELECT count(*) FROM onek_with_null WHERE unique1 IS NULL AND unique2 IN (-1, 0, 1);
DROP INDEX onek_nulltest;
@ -753,7 +754,7 @@ SELECT count(*) FROM dupindexcols
WHERE f1 BETWEEN 'WA' AND 'ZZZ' and id < 1000 and f1 ~<~ 'YX';
--
-- Check ordering of =ANY indexqual results (bug in 9.2.0)
-- Check that index scans with =ANY indexquals return rows in index order
--
explain (costs off)
@ -765,6 +766,7 @@ SELECT unique1 FROM tenk1
WHERE unique1 IN (1,42,7)
ORDER BY unique1;
-- Non-required array scan key on "tenthous":
explain (costs off)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
@ -774,18 +776,68 @@ SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
SET enable_indexonlyscan = OFF;
-- Non-required array scan key on "tenthous", backward scan:
explain (costs off)
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
ORDER BY thousand DESC, tenthous DESC;
SELECT thousand, tenthous FROM tenk1
WHERE thousand < 2 AND tenthous IN (1001,3000)
ORDER BY thousand;
ORDER BY thousand DESC, tenthous DESC;
RESET enable_indexonlyscan;
--
-- Check elimination of redundant and contradictory index quals
--
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}');
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = ANY('{7, 8, 9}');
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]);
SELECT unique1 FROM tenk1 WHERE unique1 = ANY('{7, 14, 22}') and unique1 = ANY('{33, 44}'::bigint[]);
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 1;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 = 12345;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 >= 42;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 > 42;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999;
SELECT unique1 FROM tenk1 WHERE unique1 > 9996 and unique1 >= 9999;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3;
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 <= 3;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint;
SELECT unique1 FROM tenk1 WHERE unique1 < 3 and unique1 < (-1)::bigint;
explain (costs off)
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint;
SELECT unique1 FROM tenk1 WHERE unique1 IN (1, 42, 7) and unique1 < (-1)::bigint;
--
-- Check elimination of constant-NULL subexpressions

View File

@ -137,11 +137,19 @@ alter table tenk2 reset (parallel_workers);
-- test parallel index scans.
set enable_seqscan to off;
set enable_bitmapscan to off;
set random_page_cost = 2;
explain (costs off)
select count((unique1)) from tenk1 where hundred > 1;
select count((unique1)) from tenk1 where hundred > 1;
-- Parallel ScalarArrayOp index scan
explain (costs off)
select count((unique1)) from tenk1
where hundred = any ((select array_agg(i) from generate_series(1, 100, 15) i)::int[]);
select count((unique1)) from tenk1
where hundred = any ((select array_agg(i) from generate_series(1, 100, 15) i)::int[]);
-- test parallel index-only scans.
explain (costs off)
select count(*) from tenk1 where thousand > 95;

View File

@ -208,8 +208,10 @@ BTPageStat
BTPageState
BTParallelScanDesc
BTPendingFSM
BTReadPageState
BTScanInsert
BTScanInsertData
BTScanKeyPreproc
BTScanOpaque
BTScanOpaqueData
BTScanPos