Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * genam.c
4 : * general index access method routines
5 : *
6 : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/index/genam.c
12 : *
13 : * NOTES
14 : * many of the old access method routines have been turned into
15 : * macros and moved to genam.h -cim 4/30/91
16 : *
17 : *-------------------------------------------------------------------------
18 : */
19 :
20 : #include "postgres.h"
21 :
22 : #include "access/genam.h"
23 : #include "access/relscan.h"
24 : #include "access/tableam.h"
25 : #include "access/transam.h"
26 : #include "catalog/index.h"
27 : #include "lib/stringinfo.h"
28 : #include "miscadmin.h"
29 : #include "storage/bufmgr.h"
30 : #include "storage/procarray.h"
31 : #include "utils/acl.h"
32 : #include "utils/lsyscache.h"
33 : #include "utils/rel.h"
34 : #include "utils/rls.h"
35 : #include "utils/ruleutils.h"
36 : #include "utils/snapmgr.h"
37 :
38 :
39 : /* ----------------------------------------------------------------
40 : * general access method routines
41 : *
42 : * All indexed access methods use an identical scan structure.
43 : * We don't know how the various AMs do locking, however, so we don't
44 : * do anything about that here.
45 : *
46 : * The intent is that an AM implementor will define a beginscan routine
47 : * that calls RelationGetIndexScan, to fill in the scan, and then does
48 : * whatever kind of locking he wants.
49 : *
50 : * At the end of a scan, the AM's endscan routine undoes the locking,
51 : * but does *not* call IndexScanEnd --- the higher-level index_endscan
52 : * routine does that. (We can't do it in the AM because index_endscan
53 : * still needs to touch the IndexScanDesc after calling the AM.)
54 : *
55 : * Because of this, the AM does not have a choice whether to call
56 : * RelationGetIndexScan or not; its beginscan routine must return an
57 : * object made by RelationGetIndexScan. This is kinda ugly but not
58 : * worth cleaning up now.
59 : * ----------------------------------------------------------------
60 : */
61 :
62 : /* ----------------
63 : * RelationGetIndexScan -- Create and fill an IndexScanDesc.
64 : *
65 : * This routine creates an index scan structure and sets up initial
66 : * contents for it.
67 : *
68 : * Parameters:
69 : * indexRelation -- index relation for scan.
70 : * nkeys -- count of scan keys (index qual conditions).
71 : * norderbys -- count of index order-by operators.
72 : *
73 : * Returns:
74 : * An initialized IndexScanDesc.
75 : * ----------------
76 : */
77 : IndexScanDesc
78 10916402 : RelationGetIndexScan(Relation indexRelation, int nkeys, int norderbys)
79 : {
80 : IndexScanDesc scan;
81 :
82 10916402 : scan = (IndexScanDesc) palloc(sizeof(IndexScanDescData));
83 :
84 10916402 : scan->heapRelation = NULL; /* may be set later */
85 10916402 : scan->xs_heapfetch = NULL;
86 10916402 : scan->indexRelation = indexRelation;
87 10916402 : scan->xs_snapshot = InvalidSnapshot; /* caller must initialize this */
88 10916402 : scan->numberOfKeys = nkeys;
89 10916402 : scan->numberOfOrderBys = norderbys;
90 :
91 : /*
92 : * We allocate key workspace here, but it won't get filled until amrescan.
93 : */
94 10916402 : if (nkeys > 0)
95 10905692 : scan->keyData = (ScanKey) palloc(sizeof(ScanKeyData) * nkeys);
96 : else
97 10710 : scan->keyData = NULL;
98 10916402 : if (norderbys > 0)
99 192 : scan->orderByData = (ScanKey) palloc(sizeof(ScanKeyData) * norderbys);
100 : else
101 10916210 : scan->orderByData = NULL;
102 :
103 10916402 : scan->xs_want_itup = false; /* may be set later */
104 :
105 : /*
106 : * During recovery we ignore killed tuples and don't bother to kill them
107 : * either. We do this because the xmin on the primary node could easily be
108 : * later than the xmin on the standby node, so that what the primary
109 : * thinks is killed is supposed to be visible on standby. So for correct
110 : * MVCC for queries during recovery we must ignore these hints and check
111 : * all tuples. Do *not* set ignore_killed_tuples to true when running in a
112 : * transaction that was started during recovery. xactStartedInRecovery
113 : * should not be altered by index AMs.
114 : */
115 10916402 : scan->kill_prior_tuple = false;
116 10916402 : scan->xactStartedInRecovery = TransactionStartedDuringRecovery();
117 10916402 : scan->ignore_killed_tuples = !scan->xactStartedInRecovery;
118 :
119 10916402 : scan->opaque = NULL;
120 :
121 10916402 : scan->xs_itup = NULL;
122 10916402 : scan->xs_itupdesc = NULL;
123 10916402 : scan->xs_hitup = NULL;
124 10916402 : scan->xs_hitupdesc = NULL;
125 :
126 10916402 : return scan;
127 : }
128 :
129 : /* ----------------
130 : * IndexScanEnd -- End an index scan.
131 : *
132 : * This routine just releases the storage acquired by
133 : * RelationGetIndexScan(). Any AM-level resources are
134 : * assumed to already have been released by the AM's
135 : * endscan routine.
136 : *
137 : * Returns:
138 : * None.
139 : * ----------------
140 : */
141 : void
142 10914812 : IndexScanEnd(IndexScanDesc scan)
143 : {
144 10914812 : if (scan->keyData != NULL)
145 10904150 : pfree(scan->keyData);
146 10914812 : if (scan->orderByData != NULL)
147 186 : pfree(scan->orderByData);
148 :
149 10914812 : pfree(scan);
150 10914812 : }
151 :
152 : /*
153 : * BuildIndexValueDescription
154 : *
155 : * Construct a string describing the contents of an index entry, in the
156 : * form "(key_name, ...)=(key_value, ...)". This is currently used
157 : * for building unique-constraint and exclusion-constraint error messages,
158 : * so only key columns of the index are checked and printed.
159 : *
160 : * Note that if the user does not have permissions to view all of the
161 : * columns involved then a NULL is returned. Returning a partial key seems
162 : * unlikely to be useful and we have no way to know which of the columns the
163 : * user provided (unlike in ExecBuildSlotValueDescription).
164 : *
165 : * The passed-in values/nulls arrays are the "raw" input to the index AM,
166 : * e.g. results of FormIndexDatum --- this is not necessarily what is stored
167 : * in the index, but it's what the user perceives to be stored.
168 : *
169 : * Note: if you change anything here, check whether
170 : * ExecBuildSlotPartitionKeyDescription() in execMain.c needs a similar
171 : * change.
172 : */
173 : char *
174 790 : BuildIndexValueDescription(Relation indexRelation,
175 : const Datum *values, const bool *isnull)
176 : {
177 : StringInfoData buf;
178 : Form_pg_index idxrec;
179 : int indnkeyatts;
180 : int i;
181 : int keyno;
182 790 : Oid indexrelid = RelationGetRelid(indexRelation);
183 : Oid indrelid;
184 : AclResult aclresult;
185 :
186 790 : indnkeyatts = IndexRelationGetNumberOfKeyAttributes(indexRelation);
187 :
188 : /*
189 : * Check permissions- if the user does not have access to view all of the
190 : * key columns then return NULL to avoid leaking data.
191 : *
192 : * First check if RLS is enabled for the relation. If so, return NULL to
193 : * avoid leaking data.
194 : *
195 : * Next we need to check table-level SELECT access and then, if there is
196 : * no access there, check column-level permissions.
197 : */
198 790 : idxrec = indexRelation->rd_index;
199 790 : indrelid = idxrec->indrelid;
200 : Assert(indexrelid == idxrec->indexrelid);
201 :
202 : /* RLS check- if RLS is enabled then we don't return anything. */
203 790 : if (check_enable_rls(indrelid, InvalidOid, true) == RLS_ENABLED)
204 12 : return NULL;
205 :
206 : /* Table-level SELECT is enough, if the user has it */
207 778 : aclresult = pg_class_aclcheck(indrelid, GetUserId(), ACL_SELECT);
208 778 : if (aclresult != ACLCHECK_OK)
209 : {
210 : /*
211 : * No table-level access, so step through the columns in the index and
212 : * make sure the user has SELECT rights on all of them.
213 : */
214 24 : for (keyno = 0; keyno < indnkeyatts; keyno++)
215 : {
216 24 : AttrNumber attnum = idxrec->indkey.values[keyno];
217 :
218 : /*
219 : * Note that if attnum == InvalidAttrNumber, then this is an index
220 : * based on an expression and we return no detail rather than try
221 : * to figure out what column(s) the expression includes and if the
222 : * user has SELECT rights on them.
223 : */
224 48 : if (attnum == InvalidAttrNumber ||
225 24 : pg_attribute_aclcheck(indrelid, attnum, GetUserId(),
226 : ACL_SELECT) != ACLCHECK_OK)
227 : {
228 : /* No access, so clean up and return */
229 12 : return NULL;
230 : }
231 : }
232 : }
233 :
234 766 : initStringInfo(&buf);
235 766 : appendStringInfo(&buf, "(%s)=(",
236 : pg_get_indexdef_columns(indexrelid, true));
237 :
238 1710 : for (i = 0; i < indnkeyatts; i++)
239 : {
240 : char *val;
241 :
242 944 : if (isnull[i])
243 18 : val = "null";
244 : else
245 : {
246 : Oid foutoid;
247 : bool typisvarlena;
248 :
249 : /*
250 : * The provided data is not necessarily of the type stored in the
251 : * index; rather it is of the index opclass's input type. So look
252 : * at rd_opcintype not the index tupdesc.
253 : *
254 : * Note: this is a bit shaky for opclasses that have pseudotype
255 : * input types such as ANYARRAY or RECORD. Currently, the
256 : * typoutput functions associated with the pseudotypes will work
257 : * okay, but we might have to try harder in future.
258 : */
259 926 : getTypeOutputInfo(indexRelation->rd_opcintype[i],
260 : &foutoid, &typisvarlena);
261 926 : val = OidOutputFunctionCall(foutoid, values[i]);
262 : }
263 :
264 944 : if (i > 0)
265 178 : appendStringInfoString(&buf, ", ");
266 944 : appendStringInfoString(&buf, val);
267 : }
268 :
269 766 : appendStringInfoChar(&buf, ')');
270 :
271 766 : return buf.data;
272 : }
273 :
274 : /*
275 : * Get the snapshotConflictHorizon from the table entries pointed to by the
276 : * index tuples being deleted using an AM-generic approach.
277 : *
278 : * This is a table_index_delete_tuples() shim used by index AMs that only need
279 : * to consult the tableam to get a snapshotConflictHorizon value, and only
280 : * expect to delete index tuples that are already known deletable (typically
281 : * due to having LP_DEAD bits set). When a snapshotConflictHorizon value
282 : * isn't needed in index AM's deletion WAL record, it is safe for it to skip
283 : * calling here entirely.
284 : *
285 : * We assume that caller index AM uses the standard IndexTuple representation,
286 : * with table TIDs stored in the t_tid field. We also expect (and assert)
287 : * that the line pointers on page for 'itemnos' offsets are already marked
288 : * LP_DEAD.
289 : */
290 : TransactionId
291 0 : index_compute_xid_horizon_for_tuples(Relation irel,
292 : Relation hrel,
293 : Buffer ibuf,
294 : OffsetNumber *itemnos,
295 : int nitems)
296 : {
297 : TM_IndexDeleteOp delstate;
298 0 : TransactionId snapshotConflictHorizon = InvalidTransactionId;
299 0 : Page ipage = BufferGetPage(ibuf);
300 : IndexTuple itup;
301 :
302 : Assert(nitems > 0);
303 :
304 0 : delstate.irel = irel;
305 0 : delstate.iblknum = BufferGetBlockNumber(ibuf);
306 0 : delstate.bottomup = false;
307 0 : delstate.bottomupfreespace = 0;
308 0 : delstate.ndeltids = 0;
309 0 : delstate.deltids = palloc(nitems * sizeof(TM_IndexDelete));
310 0 : delstate.status = palloc(nitems * sizeof(TM_IndexStatus));
311 :
312 : /* identify what the index tuples about to be deleted point to */
313 0 : for (int i = 0; i < nitems; i++)
314 : {
315 0 : OffsetNumber offnum = itemnos[i];
316 : ItemId iitemid;
317 :
318 0 : iitemid = PageGetItemId(ipage, offnum);
319 0 : itup = (IndexTuple) PageGetItem(ipage, iitemid);
320 :
321 : Assert(ItemIdIsDead(iitemid));
322 :
323 0 : ItemPointerCopy(&itup->t_tid, &delstate.deltids[i].tid);
324 0 : delstate.deltids[i].id = delstate.ndeltids;
325 0 : delstate.status[i].idxoffnum = offnum;
326 0 : delstate.status[i].knowndeletable = true; /* LP_DEAD-marked */
327 0 : delstate.status[i].promising = false; /* unused */
328 0 : delstate.status[i].freespace = 0; /* unused */
329 :
330 0 : delstate.ndeltids++;
331 : }
332 :
333 : /* determine the actual xid horizon */
334 0 : snapshotConflictHorizon = table_index_delete_tuples(hrel, &delstate);
335 :
336 : /* assert tableam agrees that all items are deletable */
337 : Assert(delstate.ndeltids == nitems);
338 :
339 0 : pfree(delstate.deltids);
340 0 : pfree(delstate.status);
341 :
342 0 : return snapshotConflictHorizon;
343 : }
344 :
345 :
346 : /* ----------------------------------------------------------------
347 : * heap-or-index-scan access to system catalogs
348 : *
349 : * These functions support system catalog accesses that normally use
350 : * an index but need to be capable of being switched to heap scans
351 : * if the system indexes are unavailable.
352 : *
353 : * The specified scan keys must be compatible with the named index.
354 : * Generally this means that they must constrain either all columns
355 : * of the index, or the first K columns of an N-column index.
356 : *
357 : * These routines could work with non-system tables, actually,
358 : * but they're only useful when there is a known index to use with
359 : * the given scan keys; so in practice they're only good for
360 : * predetermined types of scans of system catalogs.
361 : * ----------------------------------------------------------------
362 : */
363 :
364 : /*
365 : * systable_beginscan --- set up for heap-or-index scan
366 : *
367 : * rel: catalog to scan, already opened and suitably locked
368 : * indexId: OID of index to conditionally use
369 : * indexOK: if false, forces a heap scan (see notes below)
370 : * snapshot: time qual to use (NULL for a recent catalog snapshot)
371 : * nkeys, key: scan keys
372 : *
373 : * The attribute numbers in the scan key should be set for the heap case.
374 : * If we choose to index, we reset them to 1..n to reference the index
375 : * columns. Note this means there must be one scankey qualification per
376 : * index column! This is checked by the Asserts in the normal, index-using
377 : * case, but won't be checked if the heapscan path is taken.
378 : *
379 : * The routine checks the normal cases for whether an indexscan is safe,
380 : * but caller can make additional checks and pass indexOK=false if needed.
381 : * In standard case indexOK can simply be constant TRUE.
382 : */
383 : SysScanDesc
384 10726614 : systable_beginscan(Relation heapRelation,
385 : Oid indexId,
386 : bool indexOK,
387 : Snapshot snapshot,
388 : int nkeys, ScanKey key)
389 : {
390 : SysScanDesc sysscan;
391 : Relation irel;
392 :
393 10726614 : if (indexOK &&
394 10543858 : !IgnoreSystemIndexes &&
395 10470498 : !ReindexIsProcessingIndex(indexId))
396 10459888 : irel = index_open(indexId, AccessShareLock);
397 : else
398 266726 : irel = NULL;
399 :
400 10726604 : sysscan = (SysScanDesc) palloc(sizeof(SysScanDescData));
401 :
402 10726604 : sysscan->heap_rel = heapRelation;
403 10726604 : sysscan->irel = irel;
404 10726604 : sysscan->slot = table_slot_create(heapRelation, NULL);
405 :
406 10726604 : if (snapshot == NULL)
407 : {
408 9745644 : Oid relid = RelationGetRelid(heapRelation);
409 :
410 9745644 : snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
411 9745644 : sysscan->snapshot = snapshot;
412 : }
413 : else
414 : {
415 : /* Caller is responsible for any snapshot. */
416 980960 : sysscan->snapshot = NULL;
417 : }
418 :
419 10726604 : if (irel)
420 : {
421 : int i;
422 :
423 : /* Change attribute numbers to be index column numbers. */
424 27959642 : for (i = 0; i < nkeys; i++)
425 : {
426 : int j;
427 :
428 26081348 : for (j = 0; j < IndexRelationGetNumberOfAttributes(irel); j++)
429 : {
430 26081348 : if (key[i].sk_attno == irel->rd_index->indkey.values[j])
431 : {
432 17499764 : key[i].sk_attno = j + 1;
433 17499764 : break;
434 : }
435 : }
436 17499764 : if (j == IndexRelationGetNumberOfAttributes(irel))
437 0 : elog(ERROR, "column is not in index");
438 : }
439 :
440 10459878 : sysscan->iscan = index_beginscan(heapRelation, irel,
441 : snapshot, nkeys, 0);
442 10459878 : index_rescan(sysscan->iscan, key, nkeys, NULL, 0);
443 10459878 : sysscan->scan = NULL;
444 : }
445 : else
446 : {
447 : /*
448 : * We disallow synchronized scans when forced to use a heapscan on a
449 : * catalog. In most cases the desired rows are near the front, so
450 : * that the unpredictable start point of a syncscan is a serious
451 : * disadvantage; and there are no compensating advantages, because
452 : * it's unlikely that such scans will occur in parallel.
453 : */
454 266726 : sysscan->scan = table_beginscan_strat(heapRelation, snapshot,
455 : nkeys, key,
456 : true, false);
457 266726 : sysscan->iscan = NULL;
458 : }
459 :
460 : /*
461 : * If CheckXidAlive is set then set a flag to indicate that system table
462 : * scan is in-progress. See detailed comments in xact.c where these
463 : * variables are declared.
464 : */
465 10726604 : if (TransactionIdIsValid(CheckXidAlive))
466 1398 : bsysscan = true;
467 :
468 10726604 : return sysscan;
469 : }
470 :
471 : /*
472 : * HandleConcurrentAbort - Handle concurrent abort of the CheckXidAlive.
473 : *
474 : * Error out, if CheckXidAlive is aborted. We can't directly use
475 : * TransactionIdDidAbort as after crash such transaction might not have been
476 : * marked as aborted. See detailed comments in xact.c where the variable
477 : * is declared.
478 : */
479 : static inline void
480 22078306 : HandleConcurrentAbort()
481 : {
482 22078306 : if (TransactionIdIsValid(CheckXidAlive) &&
483 2012 : !TransactionIdIsInProgress(CheckXidAlive) &&
484 14 : !TransactionIdDidCommit(CheckXidAlive))
485 14 : ereport(ERROR,
486 : (errcode(ERRCODE_TRANSACTION_ROLLBACK),
487 : errmsg("transaction aborted during system catalog scan")));
488 22078292 : }
489 :
490 : /*
491 : * systable_getnext --- get next tuple in a heap-or-index scan
492 : *
493 : * Returns NULL if no more tuples available.
494 : *
495 : * Note that returned tuple is a reference to data in a disk buffer;
496 : * it must not be modified, and should be presumed inaccessible after
497 : * next getnext() or endscan() call.
498 : *
499 : * XXX: It'd probably make sense to offer a slot based interface, at least
500 : * optionally.
501 : */
502 : HeapTuple
503 21671604 : systable_getnext(SysScanDesc sysscan)
504 : {
505 21671604 : HeapTuple htup = NULL;
506 :
507 21671604 : if (sysscan->irel)
508 : {
509 19929802 : if (index_getnext_slot(sysscan->iscan, ForwardScanDirection, sysscan->slot))
510 : {
511 : bool shouldFree;
512 :
513 14936898 : htup = ExecFetchSlotHeapTuple(sysscan->slot, false, &shouldFree);
514 : Assert(!shouldFree);
515 :
516 : /*
517 : * We currently don't need to support lossy index operators for
518 : * any system catalog scan. It could be done here, using the scan
519 : * keys to drive the operator calls, if we arranged to save the
520 : * heap attnums during systable_beginscan(); this is practical
521 : * because we still wouldn't need to support indexes on
522 : * expressions.
523 : */
524 14936898 : if (sysscan->iscan->xs_recheck)
525 0 : elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
526 : }
527 : }
528 : else
529 : {
530 1741802 : if (table_scan_getnextslot(sysscan->scan, ForwardScanDirection, sysscan->slot))
531 : {
532 : bool shouldFree;
533 :
534 1671032 : htup = ExecFetchSlotHeapTuple(sysscan->slot, false, &shouldFree);
535 : Assert(!shouldFree);
536 : }
537 : }
538 :
539 : /*
540 : * Handle the concurrent abort while fetching the catalog tuple during
541 : * logical streaming of a transaction.
542 : */
543 21671600 : HandleConcurrentAbort();
544 :
545 21671586 : return htup;
546 : }
547 :
548 : /*
549 : * systable_recheck_tuple --- recheck visibility of most-recently-fetched tuple
550 : *
551 : * In particular, determine if this tuple would be visible to a catalog scan
552 : * that started now. We don't handle the case of a non-MVCC scan snapshot,
553 : * because no caller needs that yet.
554 : *
555 : * This is useful to test whether an object was deleted while we waited to
556 : * acquire lock on it.
557 : *
558 : * Note: we don't actually *need* the tuple to be passed in, but it's a
559 : * good crosscheck that the caller is interested in the right tuple.
560 : */
561 : bool
562 201114 : systable_recheck_tuple(SysScanDesc sysscan, HeapTuple tup)
563 : {
564 : Snapshot freshsnap;
565 : bool result;
566 :
567 : Assert(tup == ExecFetchSlotHeapTuple(sysscan->slot, false, NULL));
568 :
569 : /*
570 : * Trust that table_tuple_satisfies_snapshot() and its subsidiaries
571 : * (commonly LockBuffer() and HeapTupleSatisfiesMVCC()) do not themselves
572 : * acquire snapshots, so we need not register the snapshot. Those
573 : * facilities are too low-level to have any business scanning tables.
574 : */
575 201114 : freshsnap = GetCatalogSnapshot(RelationGetRelid(sysscan->heap_rel));
576 :
577 201114 : result = table_tuple_satisfies_snapshot(sysscan->heap_rel,
578 201114 : sysscan->slot,
579 : freshsnap);
580 :
581 : /*
582 : * Handle the concurrent abort while fetching the catalog tuple during
583 : * logical streaming of a transaction.
584 : */
585 201114 : HandleConcurrentAbort();
586 :
587 201114 : return result;
588 : }
589 :
590 : /*
591 : * systable_endscan --- close scan, release resources
592 : *
593 : * Note that it's still up to the caller to close the heap relation.
594 : */
595 : void
596 10726040 : systable_endscan(SysScanDesc sysscan)
597 : {
598 10726040 : if (sysscan->slot)
599 : {
600 10726040 : ExecDropSingleTupleTableSlot(sysscan->slot);
601 10726040 : sysscan->slot = NULL;
602 : }
603 :
604 10726040 : if (sysscan->irel)
605 : {
606 10459330 : index_endscan(sysscan->iscan);
607 10459330 : index_close(sysscan->irel, AccessShareLock);
608 : }
609 : else
610 266710 : table_endscan(sysscan->scan);
611 :
612 10726040 : if (sysscan->snapshot)
613 9745080 : UnregisterSnapshot(sysscan->snapshot);
614 :
615 : /*
616 : * Reset the bsysscan flag at the end of the systable scan. See detailed
617 : * comments in xact.c where these variables are declared.
618 : */
619 10726040 : if (TransactionIdIsValid(CheckXidAlive))
620 1384 : bsysscan = false;
621 :
622 10726040 : pfree(sysscan);
623 10726040 : }
624 :
625 :
626 : /*
627 : * systable_beginscan_ordered --- set up for ordered catalog scan
628 : *
629 : * These routines have essentially the same API as systable_beginscan etc,
630 : * except that they guarantee to return multiple matching tuples in
631 : * index order. Also, for largely historical reasons, the index to use
632 : * is opened and locked by the caller, not here.
633 : *
634 : * Currently we do not support non-index-based scans here. (In principle
635 : * we could do a heapscan and sort, but the uses are in places that
636 : * probably don't need to still work with corrupted catalog indexes.)
637 : * For the moment, therefore, these functions are merely the thinest of
638 : * wrappers around index_beginscan/index_getnext_slot. The main reason for
639 : * their existence is to centralize possible future support of lossy operators
640 : * in catalog scans.
641 : */
642 : SysScanDesc
643 51584 : systable_beginscan_ordered(Relation heapRelation,
644 : Relation indexRelation,
645 : Snapshot snapshot,
646 : int nkeys, ScanKey key)
647 : {
648 : SysScanDesc sysscan;
649 : int i;
650 :
651 : /* REINDEX can probably be a hard error here ... */
652 51584 : if (ReindexIsProcessingIndex(RelationGetRelid(indexRelation)))
653 0 : ereport(ERROR,
654 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
655 : errmsg("cannot access index \"%s\" while it is being reindexed",
656 : RelationGetRelationName(indexRelation))));
657 : /* ... but we only throw a warning about violating IgnoreSystemIndexes */
658 51584 : if (IgnoreSystemIndexes)
659 0 : elog(WARNING, "using index \"%s\" despite IgnoreSystemIndexes",
660 : RelationGetRelationName(indexRelation));
661 :
662 51584 : sysscan = (SysScanDesc) palloc(sizeof(SysScanDescData));
663 :
664 51584 : sysscan->heap_rel = heapRelation;
665 51584 : sysscan->irel = indexRelation;
666 51584 : sysscan->slot = table_slot_create(heapRelation, NULL);
667 :
668 51584 : if (snapshot == NULL)
669 : {
670 7900 : Oid relid = RelationGetRelid(heapRelation);
671 :
672 7900 : snapshot = RegisterSnapshot(GetCatalogSnapshot(relid));
673 7900 : sysscan->snapshot = snapshot;
674 : }
675 : else
676 : {
677 : /* Caller is responsible for any snapshot. */
678 43684 : sysscan->snapshot = NULL;
679 : }
680 :
681 : /* Change attribute numbers to be index column numbers. */
682 100358 : for (i = 0; i < nkeys; i++)
683 : {
684 : int j;
685 :
686 52004 : for (j = 0; j < IndexRelationGetNumberOfAttributes(indexRelation); j++)
687 : {
688 52004 : if (key[i].sk_attno == indexRelation->rd_index->indkey.values[j])
689 : {
690 48774 : key[i].sk_attno = j + 1;
691 48774 : break;
692 : }
693 : }
694 48774 : if (j == IndexRelationGetNumberOfAttributes(indexRelation))
695 0 : elog(ERROR, "column is not in index");
696 : }
697 :
698 51584 : sysscan->iscan = index_beginscan(heapRelation, indexRelation,
699 : snapshot, nkeys, 0);
700 51584 : index_rescan(sysscan->iscan, key, nkeys, NULL, 0);
701 51584 : sysscan->scan = NULL;
702 :
703 51584 : return sysscan;
704 : }
705 :
706 : /*
707 : * systable_getnext_ordered --- get next tuple in an ordered catalog scan
708 : */
709 : HeapTuple
710 205598 : systable_getnext_ordered(SysScanDesc sysscan, ScanDirection direction)
711 : {
712 205598 : HeapTuple htup = NULL;
713 :
714 : Assert(sysscan->irel);
715 205598 : if (index_getnext_slot(sysscan->iscan, direction, sysscan->slot))
716 155244 : htup = ExecFetchSlotHeapTuple(sysscan->slot, false, NULL);
717 :
718 : /* See notes in systable_getnext */
719 205592 : if (htup && sysscan->iscan->xs_recheck)
720 0 : elog(ERROR, "system catalog scans with lossy index conditions are not implemented");
721 :
722 : /*
723 : * Handle the concurrent abort while fetching the catalog tuple during
724 : * logical streaming of a transaction.
725 : */
726 205592 : HandleConcurrentAbort();
727 :
728 205592 : return htup;
729 : }
730 :
731 : /*
732 : * systable_endscan_ordered --- close scan, release resources
733 : */
734 : void
735 51566 : systable_endscan_ordered(SysScanDesc sysscan)
736 : {
737 51566 : if (sysscan->slot)
738 : {
739 51566 : ExecDropSingleTupleTableSlot(sysscan->slot);
740 51566 : sysscan->slot = NULL;
741 : }
742 :
743 : Assert(sysscan->irel);
744 51566 : index_endscan(sysscan->iscan);
745 51566 : if (sysscan->snapshot)
746 7888 : UnregisterSnapshot(sysscan->snapshot);
747 51566 : pfree(sysscan);
748 51566 : }
|