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