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