Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nodeIndexonlyscan.c
4 : : * Routines to support index-only scans
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/executor/nodeIndexonlyscan.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : /*
16 : : * INTERFACE ROUTINES
17 : : * ExecIndexOnlyScan scans an index
18 : : * IndexOnlyNext retrieve next tuple
19 : : * ExecInitIndexOnlyScan creates and initializes state info.
20 : : * ExecReScanIndexOnlyScan rescans the indexed relation.
21 : : * ExecEndIndexOnlyScan releases all storage.
22 : : * ExecIndexOnlyMarkPos marks scan position.
23 : : * ExecIndexOnlyRestrPos restores scan position.
24 : : * ExecIndexOnlyScanEstimate estimates DSM space needed for
25 : : * parallel index-only scan
26 : : * ExecIndexOnlyScanInitializeDSM initialize DSM for parallel
27 : : * index-only scan
28 : : * ExecIndexOnlyScanReInitializeDSM reinitialize DSM for fresh scan
29 : : * ExecIndexOnlyScanInitializeWorker attach to DSM info in parallel worker
30 : : */
31 : : #include "postgres.h"
32 : :
33 : : #include "access/genam.h"
34 : : #include "access/htup_details.h"
35 : : #include "access/relscan.h"
36 : : #include "access/tableam.h"
37 : : #include "access/tupdesc.h"
38 : : #include "access/visibilitymap.h"
39 : : #include "catalog/pg_type.h"
40 : : #include "executor/executor.h"
41 : : #include "executor/instrument.h"
42 : : #include "executor/nodeIndexonlyscan.h"
43 : : #include "executor/nodeIndexscan.h"
44 : : #include "miscadmin.h"
45 : : #include "storage/bufmgr.h"
46 : : #include "storage/predicate.h"
47 : : #include "utils/builtins.h"
48 : : #include "utils/rel.h"
49 : :
50 : :
51 : : static TupleTableSlot *IndexOnlyNext(IndexOnlyScanState *node);
52 : : static void StoreIndexTuple(IndexOnlyScanState *node, TupleTableSlot *slot,
53 : : IndexScanDesc scandesc);
54 : :
55 : :
56 : : /* ----------------------------------------------------------------
57 : : * IndexOnlyNext
58 : : *
59 : : * Retrieve a tuple from the IndexOnlyScan node's index.
60 : : * ----------------------------------------------------------------
61 : : */
62 : : static TupleTableSlot *
63 : 3950435 : IndexOnlyNext(IndexOnlyScanState *node)
64 : : {
65 : : EState *estate;
66 : : ExprContext *econtext;
67 : : ScanDirection direction;
68 : : IndexScanDesc scandesc;
69 : : TupleTableSlot *slot;
70 : : ItemPointer tid;
71 : :
72 : : /*
73 : : * extract necessary information from index scan node
74 : : */
75 : 3950435 : estate = node->ss.ps.state;
76 : :
77 : : /*
78 : : * Determine which direction to scan the index in based on the plan's scan
79 : : * direction and the current direction of execution.
80 : : */
81 : 3950435 : direction = ScanDirectionCombine(estate->es_direction,
82 : : ((IndexOnlyScan *) node->ss.ps.plan)->indexorderdir);
83 : 3950435 : scandesc = node->ioss_ScanDesc;
84 : 3950435 : econtext = node->ss.ps.ps_ExprContext;
85 : 3950435 : slot = node->ss.ss_ScanTupleSlot;
86 : :
87 [ + + ]: 3950435 : if (scandesc == NULL)
88 : : {
89 : : /*
90 : : * We reach here if the index only scan is not parallel, or if we're
91 : : * serially executing an index only scan that was planned to be
92 : : * parallel.
93 : : */
94 [ + - ]: 6756 : scandesc = index_beginscan(node->ss.ss_currentRelation,
95 : : node->ioss_RelationDesc,
96 : : estate->es_snapshot,
97 : : node->ioss_Instrument,
98 : : node->ioss_NumScanKeys,
99 : : node->ioss_NumOrderByKeys,
100 : 6756 : ScanRelIsReadOnly(&node->ss) ?
101 : : SO_HINT_REL_READ_ONLY : SO_NONE);
102 : :
103 : 6756 : node->ioss_ScanDesc = scandesc;
104 : :
105 : :
106 : : /* Set it up for index-only scan */
107 : 6756 : node->ioss_ScanDesc->xs_want_itup = true;
108 : 6756 : node->ioss_VMBuffer = InvalidBuffer;
109 : :
110 : : /*
111 : : * If no run-time keys to calculate or they are ready, go ahead and
112 : : * pass the scankeys to the index AM.
113 : : */
114 [ + + + - ]: 6756 : if (node->ioss_NumRuntimeKeys == 0 || node->ioss_RuntimeKeysReady)
115 : 6756 : index_rescan(scandesc,
116 : : node->ioss_ScanKeys,
117 : : node->ioss_NumScanKeys,
118 : : node->ioss_OrderByKeys,
119 : : node->ioss_NumOrderByKeys);
120 : : }
121 : :
122 : : /*
123 : : * OK, now that we have what we need, fetch the next tuple.
124 : : */
125 [ + + ]: 4029725 : while ((tid = index_getnext_tid(scandesc, direction)) != NULL)
126 : : {
127 : 3898849 : bool tuple_from_heap = false;
128 : :
129 [ - + ]: 3898849 : CHECK_FOR_INTERRUPTS();
130 : :
131 : : /*
132 : : * We can skip the heap fetch if the TID references a heap page on
133 : : * which all tuples are known visible to everybody. In any case,
134 : : * we'll use the index tuple not the heap tuple as the data source.
135 : : *
136 : : * Note on Memory Ordering Effects: visibilitymap_get_status does not
137 : : * lock the visibility map buffer, and therefore the result we read
138 : : * here could be slightly stale. However, it can't be stale enough to
139 : : * matter; see comments above visibilitymap_get_status for the full
140 : : * argument. It's worth going through this complexity to avoid
141 : : * needing to lock the VM buffer, which could cause significant
142 : : * contention.
143 : : */
144 [ + + ]: 3898849 : if (!VM_ALL_VISIBLE(scandesc->heapRelation,
145 : : ItemPointerGetBlockNumber(tid),
146 : : &node->ioss_VMBuffer))
147 : : {
148 : : /*
149 : : * Rats, we have to visit the heap to check visibility.
150 : : */
151 [ + + ]: 589756 : InstrCountTuples2(node, 1);
152 [ + + ]: 589756 : if (!index_fetch_heap(scandesc, node->ioss_TableSlot))
153 : 79286 : continue; /* no visible tuple, try next index entry */
154 : :
155 : 510470 : ExecClearTuple(node->ioss_TableSlot);
156 : :
157 : : /*
158 : : * Only MVCC snapshots are supported here, so there should be no
159 : : * need to keep following the HOT chain once a visible entry has
160 : : * been found. If we did want to allow that, we'd need to keep
161 : : * more state to remember not to call index_getnext_tid next time.
162 : : */
163 [ - + ]: 510470 : if (scandesc->xs_heap_continue)
164 [ # # ]: 0 : elog(ERROR, "non-MVCC snapshots are not supported in index-only scans");
165 : :
166 : : /*
167 : : * Note: at this point we are holding a pin on the heap page, as
168 : : * recorded in scandesc->xs_cbuf. We could release that pin now,
169 : : * but it's not clear whether it's a win to do so. The next index
170 : : * entry might require a visit to the same heap page.
171 : : */
172 : :
173 : 510470 : tuple_from_heap = true;
174 : : }
175 : :
176 : : /* Fill the scan tuple slot with data from the index */
177 : 3819563 : StoreIndexTuple(node, slot, scandesc);
178 : :
179 : : /*
180 : : * If the index was lossy, we have to recheck the index quals.
181 : : */
182 [ + + ]: 3819563 : if (scandesc->xs_recheck)
183 : : {
184 : 9 : econtext->ecxt_scantuple = slot;
185 [ + + ]: 9 : if (!ExecQualAndReset(node->recheckqual, econtext))
186 : : {
187 : : /* Fails recheck, so drop it and loop back for another */
188 [ - + ]: 4 : InstrCountFiltered2(node, 1);
189 : 4 : continue;
190 : : }
191 : : }
192 : :
193 : : /*
194 : : * We don't currently support rechecking ORDER BY distances. (In
195 : : * principle, if the index can support retrieval of the originally
196 : : * indexed value, it should be able to produce an exact distance
197 : : * calculation too. So it's not clear that adding code here for
198 : : * recheck/re-sort would be worth the trouble. But we should at least
199 : : * throw an error if someone tries it.)
200 : : */
201 [ + + + + ]: 3819559 : if (scandesc->numberOfOrderBys > 0 && scandesc->xs_recheckorderby)
202 [ + - ]: 4 : ereport(ERROR,
203 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
204 : : errmsg("lossy distance functions are not supported in index-only scans")));
205 : :
206 : : /*
207 : : * If we didn't access the heap, then we'll need to take a predicate
208 : : * lock explicitly, as if we had. For now we do that at page level.
209 : : */
210 [ + + ]: 3819555 : if (!tuple_from_heap)
211 : 3309093 : PredicateLockPage(scandesc->heapRelation,
212 : : ItemPointerGetBlockNumber(tid),
213 : : estate->es_snapshot);
214 : :
215 : 3819555 : return slot;
216 : : }
217 : :
218 : : /*
219 : : * if we get here it means the index scan failed so we are at the end of
220 : : * the scan..
221 : : */
222 : 130876 : return ExecClearTuple(slot);
223 : : }
224 : :
225 : : /*
226 : : * StoreIndexTuple
227 : : * Fill the slot with the data the index AM returned.
228 : : *
229 : : * The data might be provided in either HeapTuple (xs_hitup) or IndexTuple
230 : : * (xs_itup) format. Conceivably an index AM might fill both fields, in which
231 : : * case we prefer the heap format, since it's probably a bit cheaper to fill a
232 : : * slot from.
233 : : *
234 : : * At some point this might be generally-useful functionality, but
235 : : * right now we don't need it elsewhere.
236 : : */
237 : : static void
238 : 3819563 : StoreIndexTuple(IndexOnlyScanState *node, TupleTableSlot *slot,
239 : : IndexScanDesc scandesc)
240 : : {
241 : 3819563 : ExecClearTuple(slot);
242 : :
243 : : /*
244 : : * We must deform the tuple using the tupdesc the index AM formed it with
245 : : * (xs_hitupdesc or xs_itupdesc), not the slot's tupdesc. The datums
246 : : * returned by the index AM must be binary compatible, but the descriptors
247 : : * may align each column differently in certain rare cases. (Actually,
248 : : * btree's "name" opclass stores cstring tuples that _aren't_ even binary
249 : : * compatible, in the strictest sense. We directly handle that here.)
250 : : */
251 [ + + ]: 3819563 : if (scandesc->xs_hitup)
252 : : {
253 : : Assert(slot->tts_tupleDescriptor->natts == scandesc->xs_hitupdesc->natts);
254 : :
255 : 969066 : heap_deform_tuple(scandesc->xs_hitup, scandesc->xs_hitupdesc,
256 : : slot->tts_values, slot->tts_isnull);
257 : : }
258 [ + - ]: 2850497 : else if (scandesc->xs_itup)
259 : : {
260 : : Assert(slot->tts_tupleDescriptor->natts == scandesc->xs_itupdesc->natts);
261 : :
262 : 2850497 : index_deform_tuple(scandesc->xs_itup, scandesc->xs_itupdesc,
263 : : slot->tts_values, slot->tts_isnull);
264 : :
265 : : /*
266 : : * Copy all name columns stored as cstrings back into a NAMEDATALEN
267 : : * byte sized allocation. We mark this branch as unlikely as
268 : : * generally "name" is used only for the system catalogs and this
269 : : * would have to be a user query running on those or some other user
270 : : * table with an index on a name column.
271 : : */
272 [ + + ]: 2850497 : if (unlikely(node->ioss_NameCStringAttNums != NULL))
273 : : {
274 : 1948 : int attcount = node->ioss_NameCStringCount;
275 : :
276 [ + + ]: 3896 : for (int idx = 0; idx < attcount; idx++)
277 : : {
278 : 1948 : int attnum = node->ioss_NameCStringAttNums[idx];
279 : : Name name;
280 : :
281 : : /* skip null Datums */
282 [ - + ]: 1948 : if (slot->tts_isnull[attnum])
283 : 0 : continue;
284 : :
285 : : /*
286 : : * allocate the NAMEDATALEN and copy the datum into that
287 : : * memory
288 : : */
289 : 1948 : name = (Name) MemoryContextAlloc(node->ss.ps.ps_ExprContext->ecxt_per_tuple_memory,
290 : : NAMEDATALEN);
291 : :
292 : : /* use namestrcpy to zero-pad all trailing bytes */
293 : 1948 : namestrcpy(name, DatumGetCString(slot->tts_values[attnum]));
294 : 1948 : slot->tts_values[attnum] = NameGetDatum(name);
295 : : }
296 : : }
297 : : }
298 : : else
299 [ # # ]: 0 : elog(ERROR, "no data returned for index-only scan");
300 : :
301 : 3819563 : ExecStoreVirtualTuple(slot);
302 : 3819563 : }
303 : :
304 : : /*
305 : : * IndexOnlyRecheck -- access method routine to recheck a tuple in EvalPlanQual
306 : : *
307 : : * This can't really happen, since an index can't supply CTID which would
308 : : * be necessary data for any potential EvalPlanQual target relation. If it
309 : : * did happen, the EPQ code would pass us the wrong data, namely a heap
310 : : * tuple not an index tuple. So throw an error.
311 : : */
312 : : static bool
313 : 0 : IndexOnlyRecheck(IndexOnlyScanState *node, TupleTableSlot *slot)
314 : : {
315 [ # # ]: 0 : elog(ERROR, "EvalPlanQual recheck is not supported in index-only scans");
316 : : return false; /* keep compiler quiet */
317 : : }
318 : :
319 : : /* ----------------------------------------------------------------
320 : : * ExecIndexOnlyScan(node)
321 : : * ----------------------------------------------------------------
322 : : */
323 : : static TupleTableSlot *
324 : 3746520 : ExecIndexOnlyScan(PlanState *pstate)
325 : : {
326 : 3746520 : IndexOnlyScanState *node = castNode(IndexOnlyScanState, pstate);
327 : :
328 : : /*
329 : : * If we have runtime keys and they've not already been set up, do it now.
330 : : */
331 [ + + + + ]: 3746520 : if (node->ioss_NumRuntimeKeys != 0 && !node->ioss_RuntimeKeysReady)
332 : 372 : ExecReScan((PlanState *) node);
333 : :
334 : 3746520 : return ExecScan(&node->ss,
335 : : (ExecScanAccessMtd) IndexOnlyNext,
336 : : (ExecScanRecheckMtd) IndexOnlyRecheck);
337 : : }
338 : :
339 : : /* ----------------------------------------------------------------
340 : : * ExecReScanIndexOnlyScan(node)
341 : : *
342 : : * Recalculates the values of any scan keys whose value depends on
343 : : * information known at runtime, then rescans the indexed relation.
344 : : *
345 : : * Updating the scan key was formerly done separately in
346 : : * ExecUpdateIndexScanKeys. Integrating it into ReScan makes
347 : : * rescans of indices and relations/general streams more uniform.
348 : : * ----------------------------------------------------------------
349 : : */
350 : : void
351 : 144796 : ExecReScanIndexOnlyScan(IndexOnlyScanState *node)
352 : : {
353 : : /*
354 : : * If we are doing runtime key calculations (ie, any of the index key
355 : : * values weren't simple Consts), compute the new key values. But first,
356 : : * reset the context so we don't leak memory as each outer tuple is
357 : : * scanned. Note this assumes that we will recalculate *all* runtime keys
358 : : * on each call.
359 : : */
360 [ + + ]: 144796 : if (node->ioss_NumRuntimeKeys != 0)
361 : : {
362 : 144724 : ExprContext *econtext = node->ioss_RuntimeContext;
363 : :
364 : 144724 : ResetExprContext(econtext);
365 : 144724 : ExecIndexEvalRuntimeKeys(econtext,
366 : : node->ioss_RuntimeKeys,
367 : : node->ioss_NumRuntimeKeys);
368 : : }
369 : 144796 : node->ioss_RuntimeKeysReady = true;
370 : :
371 : : /* reset index scan */
372 [ + + ]: 144796 : if (node->ioss_ScanDesc)
373 : 143176 : index_rescan(node->ioss_ScanDesc,
374 : : node->ioss_ScanKeys, node->ioss_NumScanKeys,
375 : : node->ioss_OrderByKeys, node->ioss_NumOrderByKeys);
376 : :
377 : 144796 : ExecScanReScan(&node->ss);
378 : 144796 : }
379 : :
380 : :
381 : : /* ----------------------------------------------------------------
382 : : * ExecEndIndexOnlyScan
383 : : * ----------------------------------------------------------------
384 : : */
385 : : void
386 : 11435 : ExecEndIndexOnlyScan(IndexOnlyScanState *node)
387 : : {
388 : : Relation indexRelationDesc;
389 : : IndexScanDesc indexScanDesc;
390 : :
391 : : /*
392 : : * extract information from the node
393 : : */
394 : 11435 : indexRelationDesc = node->ioss_RelationDesc;
395 : 11435 : indexScanDesc = node->ioss_ScanDesc;
396 : :
397 : : /* Release VM buffer pin, if any. */
398 [ + + ]: 11435 : if (node->ioss_VMBuffer != InvalidBuffer)
399 : : {
400 : 4834 : ReleaseBuffer(node->ioss_VMBuffer);
401 : 4834 : node->ioss_VMBuffer = InvalidBuffer;
402 : : }
403 : :
404 : : /*
405 : : * When ending a parallel worker, copy the statistics gathered by the
406 : : * worker back into shared memory so that it can be picked up by the main
407 : : * process to report in EXPLAIN ANALYZE
408 : : */
409 [ - + - - ]: 11435 : if (node->ioss_SharedInfo != NULL && IsParallelWorker())
410 : : {
411 : : IndexScanInstrumentation *winstrument;
412 : :
413 : : Assert(ParallelWorkerNumber < node->ioss_SharedInfo->num_workers);
414 : 0 : winstrument = &node->ioss_SharedInfo->winstrument[ParallelWorkerNumber];
415 : :
416 : : /*
417 : : * We have to accumulate the stats rather than performing a memcpy.
418 : : * When a Gather/GatherMerge node finishes it will perform planner
419 : : * shutdown on the workers. On rescan it will spin up new workers
420 : : * which will have a new IndexOnlyScanState and zeroed stats.
421 : : */
422 : 0 : winstrument->nsearches += node->ioss_Instrument->nsearches;
423 : : }
424 : :
425 : : /*
426 : : * close the index relation (no-op if we didn't open it)
427 : : */
428 [ + + ]: 11435 : if (indexScanDesc)
429 : 6889 : index_endscan(indexScanDesc);
430 [ + + ]: 11435 : if (indexRelationDesc)
431 : 9618 : index_close(indexRelationDesc, NoLock);
432 : 11435 : }
433 : :
434 : : /* ----------------------------------------------------------------
435 : : * ExecIndexOnlyMarkPos
436 : : *
437 : : * Note: we assume that no caller attempts to set a mark before having read
438 : : * at least one tuple. Otherwise, ioss_ScanDesc might still be NULL.
439 : : * ----------------------------------------------------------------
440 : : */
441 : : void
442 : 82019 : ExecIndexOnlyMarkPos(IndexOnlyScanState *node)
443 : : {
444 : 82019 : EState *estate = node->ss.ps.state;
445 : 82019 : EPQState *epqstate = estate->es_epq_active;
446 : :
447 [ - + ]: 82019 : if (epqstate != NULL)
448 : : {
449 : : /*
450 : : * We are inside an EvalPlanQual recheck. If a test tuple exists for
451 : : * this relation, then we shouldn't access the index at all. We would
452 : : * instead need to save, and later restore, the state of the
453 : : * relsubs_done flag, so that re-fetching the test tuple is possible.
454 : : * However, given the assumption that no caller sets a mark at the
455 : : * start of the scan, we can only get here with relsubs_done[i]
456 : : * already set, and so no state need be saved.
457 : : */
458 : 0 : Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
459 : :
460 : : Assert(scanrelid > 0);
461 [ # # ]: 0 : if (epqstate->relsubs_slot[scanrelid - 1] != NULL ||
462 [ # # ]: 0 : epqstate->relsubs_rowmark[scanrelid - 1] != NULL)
463 : : {
464 : : /* Verify the claim above */
465 [ # # ]: 0 : if (!epqstate->relsubs_done[scanrelid - 1])
466 [ # # ]: 0 : elog(ERROR, "unexpected ExecIndexOnlyMarkPos call in EPQ recheck");
467 : 0 : return;
468 : : }
469 : : }
470 : :
471 : 82019 : index_markpos(node->ioss_ScanDesc);
472 : : }
473 : :
474 : : /* ----------------------------------------------------------------
475 : : * ExecIndexOnlyRestrPos
476 : : * ----------------------------------------------------------------
477 : : */
478 : : void
479 : 0 : ExecIndexOnlyRestrPos(IndexOnlyScanState *node)
480 : : {
481 : 0 : EState *estate = node->ss.ps.state;
482 : 0 : EPQState *epqstate = estate->es_epq_active;
483 : :
484 [ # # ]: 0 : if (estate->es_epq_active != NULL)
485 : : {
486 : : /* See comments in ExecIndexMarkPos */
487 : 0 : Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
488 : :
489 : : Assert(scanrelid > 0);
490 [ # # ]: 0 : if (epqstate->relsubs_slot[scanrelid - 1] != NULL ||
491 [ # # ]: 0 : epqstate->relsubs_rowmark[scanrelid - 1] != NULL)
492 : : {
493 : : /* Verify the claim above */
494 [ # # ]: 0 : if (!epqstate->relsubs_done[scanrelid - 1])
495 [ # # ]: 0 : elog(ERROR, "unexpected ExecIndexOnlyRestrPos call in EPQ recheck");
496 : 0 : return;
497 : : }
498 : : }
499 : :
500 : 0 : index_restrpos(node->ioss_ScanDesc);
501 : : }
502 : :
503 : : /* ----------------------------------------------------------------
504 : : * ExecInitIndexOnlyScan
505 : : *
506 : : * Initializes the index scan's state information, creates
507 : : * scan keys, and opens the base and index relations.
508 : : *
509 : : * Note: index scans have 2 sets of state information because
510 : : * we have to keep track of the base relation and the
511 : : * index relation.
512 : : * ----------------------------------------------------------------
513 : : */
514 : : IndexOnlyScanState *
515 : 11468 : ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
516 : : {
517 : : IndexOnlyScanState *indexstate;
518 : : Relation currentRelation;
519 : : Relation indexRelation;
520 : : LOCKMODE lockmode;
521 : : TupleDesc tupDesc;
522 : : int indnkeyatts;
523 : : int namecount;
524 : :
525 : : /*
526 : : * create state structure
527 : : */
528 : 11468 : indexstate = makeNode(IndexOnlyScanState);
529 : 11468 : indexstate->ss.ps.plan = (Plan *) node;
530 : 11468 : indexstate->ss.ps.state = estate;
531 : 11468 : indexstate->ss.ps.ExecProcNode = ExecIndexOnlyScan;
532 : :
533 : : /*
534 : : * Miscellaneous initialization
535 : : *
536 : : * create expression context for node
537 : : */
538 : 11468 : ExecAssignExprContext(estate, &indexstate->ss.ps);
539 : :
540 : : /*
541 : : * open the scan relation
542 : : */
543 : 11468 : currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
544 : :
545 : 11468 : indexstate->ss.ss_currentRelation = currentRelation;
546 : 11468 : indexstate->ss.ss_currentScanDesc = NULL; /* no heap scan here */
547 : :
548 : : /*
549 : : * Build the scan tuple type using the indextlist generated by the
550 : : * planner. We use this, rather than the index's physical tuple
551 : : * descriptor, because the latter contains storage column types not the
552 : : * types of the original datums. (It's the AM's responsibility to return
553 : : * suitable data anyway.)
554 : : */
555 : 11468 : tupDesc = ExecTypeFromTL(node->indextlist);
556 : 11468 : ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc,
557 : : &TTSOpsVirtual,
558 : : 0);
559 : :
560 : : /*
561 : : * We need another slot, in a format that's suitable for the table AM, for
562 : : * when we need to fetch a tuple from the table for rechecking visibility.
563 : : */
564 : 11468 : indexstate->ioss_TableSlot =
565 : 11468 : ExecAllocTableSlot(&estate->es_tupleTable,
566 : : RelationGetDescr(currentRelation),
567 : : table_slot_callbacks(currentRelation), 0);
568 : :
569 : : /*
570 : : * Initialize result type and projection info. The node's targetlist will
571 : : * contain Vars with varno = INDEX_VAR, referencing the scan tuple.
572 : : */
573 : 11468 : ExecInitResultTypeTL(&indexstate->ss.ps);
574 : 11468 : ExecAssignScanProjectionInfoWithVarno(&indexstate->ss, INDEX_VAR);
575 : :
576 : : /*
577 : : * initialize child expressions
578 : : *
579 : : * Note: we don't initialize all of the indexorderby expression, only the
580 : : * sub-parts corresponding to runtime keys (see below).
581 : : */
582 : 11468 : indexstate->ss.ps.qual =
583 : 11468 : ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
584 : 11468 : indexstate->recheckqual =
585 : 11468 : ExecInitQual(node->recheckqual, (PlanState *) indexstate);
586 : :
587 : : /*
588 : : * If we are just doing EXPLAIN (ie, aren't going to run the plan), stop
589 : : * here. This allows an index-advisor plugin to EXPLAIN a plan containing
590 : : * references to nonexistent indexes.
591 : : */
592 [ + + ]: 11468 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
593 : 1817 : return indexstate;
594 : :
595 : : /* Set up instrumentation of index-only scans if requested */
596 [ + + ]: 9651 : if (estate->es_instrument)
597 : 80 : indexstate->ioss_Instrument = palloc0_object(IndexScanInstrumentation);
598 : :
599 : : /* Open the index relation. */
600 : 9651 : lockmode = exec_rt_fetch(node->scan.scanrelid, estate)->rellockmode;
601 : 9651 : indexRelation = index_open(node->indexid, lockmode);
602 : 9651 : indexstate->ioss_RelationDesc = indexRelation;
603 : :
604 : : /*
605 : : * Initialize index-specific scan state
606 : : */
607 : 9651 : indexstate->ioss_RuntimeKeysReady = false;
608 : 9651 : indexstate->ioss_RuntimeKeys = NULL;
609 : 9651 : indexstate->ioss_NumRuntimeKeys = 0;
610 : :
611 : : /*
612 : : * build the index scan keys from the index qualification
613 : : */
614 : 9651 : ExecIndexBuildScanKeys((PlanState *) indexstate,
615 : : indexRelation,
616 : : node->indexqual,
617 : : false,
618 : 9651 : &indexstate->ioss_ScanKeys,
619 : : &indexstate->ioss_NumScanKeys,
620 : : &indexstate->ioss_RuntimeKeys,
621 : : &indexstate->ioss_NumRuntimeKeys,
622 : : NULL, /* no ArrayKeys */
623 : : NULL);
624 : :
625 : : /*
626 : : * any ORDER BY exprs have to be turned into scankeys in the same way
627 : : */
628 : 9651 : ExecIndexBuildScanKeys((PlanState *) indexstate,
629 : : indexRelation,
630 : : node->indexorderby,
631 : : true,
632 : 9651 : &indexstate->ioss_OrderByKeys,
633 : : &indexstate->ioss_NumOrderByKeys,
634 : : &indexstate->ioss_RuntimeKeys,
635 : : &indexstate->ioss_NumRuntimeKeys,
636 : : NULL, /* no ArrayKeys */
637 : : NULL);
638 : :
639 : : /*
640 : : * If we have runtime keys, we need an ExprContext to evaluate them. The
641 : : * node's standard context won't do because we want to reset that context
642 : : * for every tuple. So, build another context just like the other one...
643 : : * -tgl 7/11/00
644 : : */
645 [ + + ]: 9651 : if (indexstate->ioss_NumRuntimeKeys != 0)
646 : : {
647 : 4218 : ExprContext *stdecontext = indexstate->ss.ps.ps_ExprContext;
648 : :
649 : 4218 : ExecAssignExprContext(estate, &indexstate->ss.ps);
650 : 4218 : indexstate->ioss_RuntimeContext = indexstate->ss.ps.ps_ExprContext;
651 : 4218 : indexstate->ss.ps.ps_ExprContext = stdecontext;
652 : : }
653 : : else
654 : : {
655 : 5433 : indexstate->ioss_RuntimeContext = NULL;
656 : : }
657 : :
658 : 9651 : indexstate->ioss_NameCStringAttNums = NULL;
659 : 9651 : indnkeyatts = indexRelation->rd_index->indnkeyatts;
660 : 9651 : namecount = 0;
661 : :
662 : : /*
663 : : * The "name" type for btree uses text_ops which results in storing
664 : : * cstrings in the indexed keys rather than names. Here we detect that in
665 : : * a generic way in case other index AMs want to do the same optimization.
666 : : * Check for opclasses with an opcintype of NAMEOID and an index tuple
667 : : * descriptor with CSTRINGOID. If any of these are found, create an array
668 : : * marking the index attribute number of each of them. StoreIndexTuple()
669 : : * handles copying the name Datums into a NAMEDATALEN-byte allocation.
670 : : */
671 : :
672 : : /* First, count the number of such index keys */
673 [ + + ]: 23989 : for (int attnum = 0; attnum < indnkeyatts; attnum++)
674 : : {
675 [ + + ]: 14338 : if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
676 [ + - ]: 1763 : indexRelation->rd_opcintype[attnum] == NAMEOID)
677 : 1763 : namecount++;
678 : : }
679 : :
680 [ + + ]: 9651 : if (namecount > 0)
681 : : {
682 : 1763 : int idx = 0;
683 : :
684 : : /*
685 : : * Now create an array to mark the attribute numbers of the keys that
686 : : * need to be converted from cstring to name.
687 : : */
688 : 1763 : indexstate->ioss_NameCStringAttNums = palloc_array(AttrNumber, namecount);
689 : :
690 [ + + ]: 5348 : for (int attnum = 0; attnum < indnkeyatts; attnum++)
691 : : {
692 [ + + ]: 3585 : if (TupleDescAttr(indexRelation->rd_att, attnum)->atttypid == CSTRINGOID &&
693 [ + - ]: 1763 : indexRelation->rd_opcintype[attnum] == NAMEOID)
694 : 1763 : indexstate->ioss_NameCStringAttNums[idx++] = (AttrNumber) attnum;
695 : : }
696 : : }
697 : :
698 : 9651 : indexstate->ioss_NameCStringCount = namecount;
699 : :
700 : : /*
701 : : * all done.
702 : : */
703 : 9651 : return indexstate;
704 : : }
705 : :
706 : : /* ----------------------------------------------------------------
707 : : * Parallel Index-only Scan Support
708 : : * ----------------------------------------------------------------
709 : : */
710 : :
711 : : /* ----------------------------------------------------------------
712 : : * ExecIndexOnlyScanEstimate
713 : : *
714 : : * Compute the amount of space we'll need in the parallel
715 : : * query DSM, and inform pcxt->estimator about our needs.
716 : : * ----------------------------------------------------------------
717 : : */
718 : : void
719 : 30 : ExecIndexOnlyScanEstimate(IndexOnlyScanState *node,
720 : : ParallelContext *pcxt)
721 : : {
722 : 30 : EState *estate = node->ss.ps.state;
723 : :
724 : 30 : node->ioss_PscanLen = index_parallelscan_estimate(node->ioss_RelationDesc,
725 : : node->ioss_NumScanKeys,
726 : : node->ioss_NumOrderByKeys,
727 : : estate->es_snapshot);
728 : 30 : shm_toc_estimate_chunk(&pcxt->estimator, node->ioss_PscanLen);
729 : 30 : shm_toc_estimate_keys(&pcxt->estimator, 1);
730 : 30 : }
731 : :
732 : : /* ----------------------------------------------------------------
733 : : * ExecIndexOnlyScanInitializeDSM
734 : : *
735 : : * Set up a parallel index-only scan descriptor.
736 : : * ----------------------------------------------------------------
737 : : */
738 : : void
739 : 30 : ExecIndexOnlyScanInitializeDSM(IndexOnlyScanState *node,
740 : : ParallelContext *pcxt)
741 : : {
742 : 30 : EState *estate = node->ss.ps.state;
743 : : ParallelIndexScanDesc piscan;
744 : :
745 : 30 : piscan = shm_toc_allocate(pcxt->toc, node->ioss_PscanLen);
746 : 30 : index_parallelscan_initialize(node->ss.ss_currentRelation,
747 : : node->ioss_RelationDesc,
748 : : estate->es_snapshot,
749 : : piscan);
750 : 30 : shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, piscan);
751 : :
752 : 30 : node->ioss_ScanDesc =
753 [ + - ]: 30 : index_beginscan_parallel(node->ss.ss_currentRelation,
754 : : node->ioss_RelationDesc,
755 : : node->ioss_Instrument,
756 : : node->ioss_NumScanKeys,
757 : : node->ioss_NumOrderByKeys,
758 : : piscan,
759 : 30 : ScanRelIsReadOnly(&node->ss) ?
760 : : SO_HINT_REL_READ_ONLY : SO_NONE);
761 : 30 : node->ioss_ScanDesc->xs_want_itup = true;
762 : 30 : node->ioss_VMBuffer = InvalidBuffer;
763 : :
764 : : /*
765 : : * If no run-time keys to calculate or they are ready, go ahead and pass
766 : : * the scankeys to the index AM.
767 : : */
768 [ - + - - ]: 30 : if (node->ioss_NumRuntimeKeys == 0 || node->ioss_RuntimeKeysReady)
769 : 30 : index_rescan(node->ioss_ScanDesc,
770 : : node->ioss_ScanKeys, node->ioss_NumScanKeys,
771 : : node->ioss_OrderByKeys, node->ioss_NumOrderByKeys);
772 : 30 : }
773 : :
774 : : /* ----------------------------------------------------------------
775 : : * ExecIndexOnlyScanReInitializeDSM
776 : : *
777 : : * Reset shared state before beginning a fresh scan.
778 : : * ----------------------------------------------------------------
779 : : */
780 : : void
781 : 8 : ExecIndexOnlyScanReInitializeDSM(IndexOnlyScanState *node,
782 : : ParallelContext *pcxt)
783 : : {
784 : : Assert(node->ss.ps.plan->parallel_aware);
785 : 8 : index_parallelrescan(node->ioss_ScanDesc);
786 : 8 : }
787 : :
788 : : /* ----------------------------------------------------------------
789 : : * ExecIndexOnlyScanInitializeWorker
790 : : *
791 : : * Copy relevant information from TOC into planstate.
792 : : * ----------------------------------------------------------------
793 : : */
794 : : void
795 : 136 : ExecIndexOnlyScanInitializeWorker(IndexOnlyScanState *node,
796 : : ParallelWorkerContext *pwcxt)
797 : : {
798 : : ParallelIndexScanDesc piscan;
799 : :
800 : 136 : piscan = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
801 : :
802 : 136 : node->ioss_ScanDesc =
803 [ + - ]: 136 : index_beginscan_parallel(node->ss.ss_currentRelation,
804 : : node->ioss_RelationDesc,
805 : : node->ioss_Instrument,
806 : : node->ioss_NumScanKeys,
807 : : node->ioss_NumOrderByKeys,
808 : : piscan,
809 : 136 : ScanRelIsReadOnly(&node->ss) ?
810 : : SO_HINT_REL_READ_ONLY : SO_NONE);
811 : 136 : node->ioss_ScanDesc->xs_want_itup = true;
812 : :
813 : : /*
814 : : * If no run-time keys to calculate or they are ready, go ahead and pass
815 : : * the scankeys to the index AM.
816 : : */
817 [ - + - - ]: 136 : if (node->ioss_NumRuntimeKeys == 0 || node->ioss_RuntimeKeysReady)
818 : 136 : index_rescan(node->ioss_ScanDesc,
819 : : node->ioss_ScanKeys, node->ioss_NumScanKeys,
820 : : node->ioss_OrderByKeys, node->ioss_NumOrderByKeys);
821 : 136 : }
822 : :
823 : : /*
824 : : * Compute the amount of space we'll need for the shared instrumentation and
825 : : * inform pcxt->estimator.
826 : : */
827 : : void
828 : 38 : ExecIndexOnlyScanInstrumentEstimate(IndexOnlyScanState *node,
829 : : ParallelContext *pcxt)
830 : : {
831 : : Size size;
832 : :
833 [ - + - - ]: 38 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
834 : 38 : return;
835 : :
836 : : /*
837 : : * This size calculation is trivial enough that we don't bother saving it
838 : : * in the IndexOnlyScanState. We'll recalculate the needed size in
839 : : * ExecIndexOnlyScanInstrumentInitDSM().
840 : : */
841 : 0 : size = add_size(offsetof(SharedIndexScanInstrumentation, winstrument),
842 : 0 : mul_size(pcxt->nworkers, sizeof(IndexScanInstrumentation)));
843 : 0 : shm_toc_estimate_chunk(&pcxt->estimator, size);
844 : 0 : shm_toc_estimate_keys(&pcxt->estimator, 1);
845 : : }
846 : :
847 : : /*
848 : : * Set up parallel index-only scan instrumentation.
849 : : */
850 : : void
851 : 38 : ExecIndexOnlyScanInstrumentInitDSM(IndexOnlyScanState *node,
852 : : ParallelContext *pcxt)
853 : : {
854 : : Size size;
855 : :
856 [ - + - - ]: 38 : if (!node->ss.ps.instrument || pcxt->nworkers == 0)
857 : 38 : return;
858 : :
859 : 0 : size = add_size(offsetof(SharedIndexScanInstrumentation, winstrument),
860 : 0 : mul_size(pcxt->nworkers, sizeof(IndexScanInstrumentation)));
861 : 0 : node->ioss_SharedInfo =
862 : 0 : (SharedIndexScanInstrumentation *) shm_toc_allocate(pcxt->toc, size);
863 : :
864 : : /* Each per-worker area must start out as zeroes */
865 : 0 : memset(node->ioss_SharedInfo, 0, size);
866 : 0 : node->ioss_SharedInfo->num_workers = pcxt->nworkers;
867 : 0 : shm_toc_insert(pcxt->toc,
868 : 0 : node->ss.ps.plan->plan_node_id +
869 : : PARALLEL_KEY_SCAN_INSTRUMENT_OFFSET,
870 : 0 : node->ioss_SharedInfo);
871 : : }
872 : :
873 : : /*
874 : : * Look up and save the location of the shared instrumentation.
875 : : */
876 : : void
877 : 160 : ExecIndexOnlyScanInstrumentInitWorker(IndexOnlyScanState *node,
878 : : ParallelWorkerContext *pwcxt)
879 : : {
880 [ + - ]: 160 : if (!node->ss.ps.instrument)
881 : 160 : return;
882 : :
883 : 0 : node->ioss_SharedInfo = (SharedIndexScanInstrumentation *)
884 : 0 : shm_toc_lookup(pwcxt->toc,
885 : 0 : node->ss.ps.plan->plan_node_id +
886 : : PARALLEL_KEY_SCAN_INSTRUMENT_OFFSET,
887 : : false);
888 : : }
889 : :
890 : : /* ----------------------------------------------------------------
891 : : * ExecIndexOnlyScanRetrieveInstrumentation
892 : : *
893 : : * Transfer index-only scan statistics from DSM to private memory.
894 : : * ----------------------------------------------------------------
895 : : */
896 : : void
897 : 0 : ExecIndexOnlyScanRetrieveInstrumentation(IndexOnlyScanState *node)
898 : : {
899 : 0 : SharedIndexScanInstrumentation *SharedInfo = node->ioss_SharedInfo;
900 : : size_t size;
901 : :
902 [ # # ]: 0 : if (SharedInfo == NULL)
903 : 0 : return;
904 : :
905 : : /* Create a copy of SharedInfo in backend-local memory */
906 : 0 : size = offsetof(SharedIndexScanInstrumentation, winstrument) +
907 : 0 : SharedInfo->num_workers * sizeof(IndexScanInstrumentation);
908 : 0 : node->ioss_SharedInfo = palloc(size);
909 : 0 : memcpy(node->ioss_SharedInfo, SharedInfo, size);
910 : : }
|