Coverage Report

Created: 2025-10-09 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/executor/nodeBitmapHeapscan.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeBitmapHeapscan.c
4
 *    Routines to support bitmapped scans of relations
5
 *
6
 * NOTE: it is critical that this plan type only be used with MVCC-compliant
7
 * snapshots (ie, regular snapshots, not SnapshotAny or one of the other
8
 * special snapshots).  The reason is that since index and heap scans are
9
 * decoupled, there can be no assurance that the index tuple prompting a
10
 * visit to a particular heap TID still exists when the visit is made.
11
 * Therefore the tuple might not exist anymore either (which is OK because
12
 * heap_fetch will cope) --- but worse, the tuple slot could have been
13
 * re-used for a newer tuple.  With an MVCC snapshot the newer tuple is
14
 * certain to fail the time qual and so it will not be mistakenly returned,
15
 * but with anything else we might return a tuple that doesn't meet the
16
 * required index qual conditions.
17
 *
18
 *
19
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
20
 * Portions Copyright (c) 1994, Regents of the University of California
21
 *
22
 *
23
 * IDENTIFICATION
24
 *    src/backend/executor/nodeBitmapHeapscan.c
25
 *
26
 *-------------------------------------------------------------------------
27
 */
28
/*
29
 * INTERFACE ROUTINES
30
 *    ExecBitmapHeapScan      scans a relation using bitmap info
31
 *    ExecBitmapHeapNext      workhorse for above
32
 *    ExecInitBitmapHeapScan    creates and initializes state info.
33
 *    ExecReScanBitmapHeapScan  prepares to rescan the plan.
34
 *    ExecEndBitmapHeapScan   releases all storage.
35
 */
36
#include "postgres.h"
37
38
#include <math.h>
39
40
#include "access/relscan.h"
41
#include "access/tableam.h"
42
#include "access/visibilitymap.h"
43
#include "executor/executor.h"
44
#include "executor/nodeBitmapHeapscan.h"
45
#include "miscadmin.h"
46
#include "pgstat.h"
47
#include "storage/bufmgr.h"
48
#include "utils/rel.h"
49
#include "utils/spccache.h"
50
51
static void BitmapTableScanSetup(BitmapHeapScanState *node);
52
static TupleTableSlot *BitmapHeapNext(BitmapHeapScanState *node);
53
static inline void BitmapDoneInitializingSharedState(ParallelBitmapHeapState *pstate);
54
static bool BitmapShouldInitializeSharedState(ParallelBitmapHeapState *pstate);
55
56
57
/*
58
 * Do the underlying index scan, build the bitmap, set up the parallel state
59
 * needed for parallel workers to iterate through the bitmap, and set up the
60
 * underlying table scan descriptor.
61
 */
62
static void
63
BitmapTableScanSetup(BitmapHeapScanState *node)
64
0
{
65
0
  TBMIterator tbmiterator = {0};
66
0
  ParallelBitmapHeapState *pstate = node->pstate;
67
0
  dsa_area   *dsa = node->ss.ps.state->es_query_dsa;
68
69
0
  if (!pstate)
70
0
  {
71
0
    node->tbm = (TIDBitmap *) MultiExecProcNode(outerPlanState(node));
72
73
0
    if (!node->tbm || !IsA(node->tbm, TIDBitmap))
74
0
      elog(ERROR, "unrecognized result from subplan");
75
0
  }
76
0
  else if (BitmapShouldInitializeSharedState(pstate))
77
0
  {
78
    /*
79
     * The leader will immediately come out of the function, but others
80
     * will be blocked until leader populates the TBM and wakes them up.
81
     */
82
0
    node->tbm = (TIDBitmap *) MultiExecProcNode(outerPlanState(node));
83
0
    if (!node->tbm || !IsA(node->tbm, TIDBitmap))
84
0
      elog(ERROR, "unrecognized result from subplan");
85
86
    /*
87
     * Prepare to iterate over the TBM. This will return the dsa_pointer
88
     * of the iterator state which will be used by multiple processes to
89
     * iterate jointly.
90
     */
91
0
    pstate->tbmiterator = tbm_prepare_shared_iterate(node->tbm);
92
93
    /* We have initialized the shared state so wake up others. */
94
0
    BitmapDoneInitializingSharedState(pstate);
95
0
  }
96
97
0
  tbmiterator = tbm_begin_iterate(node->tbm, dsa,
98
0
                  pstate ?
99
0
                  pstate->tbmiterator :
100
0
                  InvalidDsaPointer);
101
102
  /*
103
   * If this is the first scan of the underlying table, create the table
104
   * scan descriptor and begin the scan.
105
   */
106
0
  if (!node->ss.ss_currentScanDesc)
107
0
  {
108
0
    node->ss.ss_currentScanDesc =
109
0
      table_beginscan_bm(node->ss.ss_currentRelation,
110
0
                 node->ss.ps.state->es_snapshot,
111
0
                 0,
112
0
                 NULL);
113
0
  }
114
115
0
  node->ss.ss_currentScanDesc->st.rs_tbmiterator = tbmiterator;
116
0
  node->initialized = true;
117
0
}
118
119
/* ----------------------------------------------------------------
120
 *    BitmapHeapNext
121
 *
122
 *    Retrieve next tuple from the BitmapHeapScan node's currentRelation
123
 * ----------------------------------------------------------------
124
 */
125
static TupleTableSlot *
126
BitmapHeapNext(BitmapHeapScanState *node)
127
0
{
128
0
  ExprContext *econtext = node->ss.ps.ps_ExprContext;
129
0
  TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
130
131
  /*
132
   * If we haven't yet performed the underlying index scan, do it, and begin
133
   * the iteration over the bitmap.
134
   */
135
0
  if (!node->initialized)
136
0
    BitmapTableScanSetup(node);
137
138
0
  while (table_scan_bitmap_next_tuple(node->ss.ss_currentScanDesc,
139
0
                    slot, &node->recheck,
140
0
                    &node->stats.lossy_pages,
141
0
                    &node->stats.exact_pages))
142
0
  {
143
    /*
144
     * Continuing in previously obtained page.
145
     */
146
0
    CHECK_FOR_INTERRUPTS();
147
148
    /*
149
     * If we are using lossy info, we have to recheck the qual conditions
150
     * at every tuple.
151
     */
152
0
    if (node->recheck)
153
0
    {
154
0
      econtext->ecxt_scantuple = slot;
155
0
      if (!ExecQualAndReset(node->bitmapqualorig, econtext))
156
0
      {
157
        /* Fails recheck, so drop it and loop back for another */
158
0
        InstrCountFiltered2(node, 1);
159
0
        ExecClearTuple(slot);
160
0
        continue;
161
0
      }
162
0
    }
163
164
    /* OK to return this tuple */
165
0
    return slot;
166
0
  }
167
168
  /*
169
   * if we get here it means we are at the end of the scan..
170
   */
171
0
  return ExecClearTuple(slot);
172
0
}
173
174
/*
175
 *  BitmapDoneInitializingSharedState - Shared state is initialized
176
 *
177
 *  By this time the leader has already populated the TBM and initialized the
178
 *  shared state so wake up other processes.
179
 */
180
static inline void
181
BitmapDoneInitializingSharedState(ParallelBitmapHeapState *pstate)
182
0
{
183
0
  SpinLockAcquire(&pstate->mutex);
184
0
  pstate->state = BM_FINISHED;
185
0
  SpinLockRelease(&pstate->mutex);
186
0
  ConditionVariableBroadcast(&pstate->cv);
187
0
}
188
189
/*
190
 * BitmapHeapRecheck -- access method routine to recheck a tuple in EvalPlanQual
191
 */
192
static bool
193
BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot)
194
0
{
195
0
  ExprContext *econtext;
196
197
  /*
198
   * extract necessary information from index scan node
199
   */
200
0
  econtext = node->ss.ps.ps_ExprContext;
201
202
  /* Does the tuple meet the original qual conditions? */
203
0
  econtext->ecxt_scantuple = slot;
204
0
  return ExecQualAndReset(node->bitmapqualorig, econtext);
205
0
}
206
207
/* ----------------------------------------------------------------
208
 *    ExecBitmapHeapScan(node)
209
 * ----------------------------------------------------------------
210
 */
211
static TupleTableSlot *
212
ExecBitmapHeapScan(PlanState *pstate)
213
0
{
214
0
  BitmapHeapScanState *node = castNode(BitmapHeapScanState, pstate);
215
216
0
  return ExecScan(&node->ss,
217
0
          (ExecScanAccessMtd) BitmapHeapNext,
218
0
          (ExecScanRecheckMtd) BitmapHeapRecheck);
219
0
}
220
221
/* ----------------------------------------------------------------
222
 *    ExecReScanBitmapHeapScan(node)
223
 * ----------------------------------------------------------------
224
 */
225
void
226
ExecReScanBitmapHeapScan(BitmapHeapScanState *node)
227
0
{
228
0
  PlanState  *outerPlan = outerPlanState(node);
229
230
0
  TableScanDesc scan = node->ss.ss_currentScanDesc;
231
232
0
  if (scan)
233
0
  {
234
    /*
235
     * End iteration on iterators saved in scan descriptor if they have
236
     * not already been cleaned up.
237
     */
238
0
    if (!tbm_exhausted(&scan->st.rs_tbmiterator))
239
0
      tbm_end_iterate(&scan->st.rs_tbmiterator);
240
241
    /* rescan to release any page pin */
242
0
    table_rescan(node->ss.ss_currentScanDesc, NULL);
243
0
  }
244
245
  /* release bitmaps and buffers if any */
246
0
  if (node->tbm)
247
0
    tbm_free(node->tbm);
248
0
  node->tbm = NULL;
249
0
  node->initialized = false;
250
0
  node->recheck = true;
251
252
0
  ExecScanReScan(&node->ss);
253
254
  /*
255
   * if chgParam of subnode is not null then plan will be re-scanned by
256
   * first ExecProcNode.
257
   */
258
0
  if (outerPlan->chgParam == NULL)
259
0
    ExecReScan(outerPlan);
260
0
}
261
262
/* ----------------------------------------------------------------
263
 *    ExecEndBitmapHeapScan
264
 * ----------------------------------------------------------------
265
 */
266
void
267
ExecEndBitmapHeapScan(BitmapHeapScanState *node)
268
0
{
269
0
  TableScanDesc scanDesc;
270
271
  /*
272
   * When ending a parallel worker, copy the statistics gathered by the
273
   * worker back into shared memory so that it can be picked up by the main
274
   * process to report in EXPLAIN ANALYZE.
275
   */
276
0
  if (node->sinstrument != NULL && IsParallelWorker())
277
0
  {
278
0
    BitmapHeapScanInstrumentation *si;
279
280
0
    Assert(ParallelWorkerNumber <= node->sinstrument->num_workers);
281
0
    si = &node->sinstrument->sinstrument[ParallelWorkerNumber];
282
283
    /*
284
     * Here we accumulate the stats rather than performing memcpy on
285
     * node->stats into si.  When a Gather/GatherMerge node finishes it
286
     * will perform planner shutdown on the workers.  On rescan it will
287
     * spin up new workers which will have a new BitmapHeapScanState and
288
     * zeroed stats.
289
     */
290
0
    si->exact_pages += node->stats.exact_pages;
291
0
    si->lossy_pages += node->stats.lossy_pages;
292
0
  }
293
294
  /*
295
   * extract information from the node
296
   */
297
0
  scanDesc = node->ss.ss_currentScanDesc;
298
299
  /*
300
   * close down subplans
301
   */
302
0
  ExecEndNode(outerPlanState(node));
303
304
0
  if (scanDesc)
305
0
  {
306
    /*
307
     * End iteration on iterators saved in scan descriptor if they have
308
     * not already been cleaned up.
309
     */
310
0
    if (!tbm_exhausted(&scanDesc->st.rs_tbmiterator))
311
0
      tbm_end_iterate(&scanDesc->st.rs_tbmiterator);
312
313
    /*
314
     * close table scan
315
     */
316
0
    table_endscan(scanDesc);
317
0
  }
318
319
  /*
320
   * release bitmaps and buffers if any
321
   */
322
0
  if (node->tbm)
323
0
    tbm_free(node->tbm);
324
0
}
325
326
/* ----------------------------------------------------------------
327
 *    ExecInitBitmapHeapScan
328
 *
329
 *    Initializes the scan's state information.
330
 * ----------------------------------------------------------------
331
 */
332
BitmapHeapScanState *
333
ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
334
0
{
335
0
  BitmapHeapScanState *scanstate;
336
0
  Relation  currentRelation;
337
338
  /* check for unsupported flags */
339
0
  Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
340
341
  /*
342
   * Assert caller didn't ask for an unsafe snapshot --- see comments at
343
   * head of file.
344
   */
345
0
  Assert(IsMVCCSnapshot(estate->es_snapshot));
346
347
  /*
348
   * create state structure
349
   */
350
0
  scanstate = makeNode(BitmapHeapScanState);
351
0
  scanstate->ss.ps.plan = (Plan *) node;
352
0
  scanstate->ss.ps.state = estate;
353
0
  scanstate->ss.ps.ExecProcNode = ExecBitmapHeapScan;
354
355
0
  scanstate->tbm = NULL;
356
357
  /* Zero the statistics counters */
358
0
  memset(&scanstate->stats, 0, sizeof(BitmapHeapScanInstrumentation));
359
360
0
  scanstate->initialized = false;
361
0
  scanstate->pstate = NULL;
362
0
  scanstate->recheck = true;
363
364
  /*
365
   * Miscellaneous initialization
366
   *
367
   * create expression context for node
368
   */
369
0
  ExecAssignExprContext(estate, &scanstate->ss.ps);
370
371
  /*
372
   * open the scan relation
373
   */
374
0
  currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags);
375
376
  /*
377
   * initialize child nodes
378
   */
379
0
  outerPlanState(scanstate) = ExecInitNode(outerPlan(node), estate, eflags);
380
381
  /*
382
   * get the scan type from the relation descriptor.
383
   */
384
0
  ExecInitScanTupleSlot(estate, &scanstate->ss,
385
0
              RelationGetDescr(currentRelation),
386
0
              table_slot_callbacks(currentRelation));
387
388
  /*
389
   * Initialize result type and projection.
390
   */
391
0
  ExecInitResultTypeTL(&scanstate->ss.ps);
392
0
  ExecAssignScanProjectionInfo(&scanstate->ss);
393
394
  /*
395
   * initialize child expressions
396
   */
397
0
  scanstate->ss.ps.qual =
398
0
    ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
399
0
  scanstate->bitmapqualorig =
400
0
    ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
401
402
0
  scanstate->ss.ss_currentRelation = currentRelation;
403
404
  /*
405
   * all done.
406
   */
407
0
  return scanstate;
408
0
}
409
410
/*----------------
411
 *    BitmapShouldInitializeSharedState
412
 *
413
 *    The first process to come here and see the state to the BM_INITIAL
414
 *    will become the leader for the parallel bitmap scan and will be
415
 *    responsible for populating the TIDBitmap.  The other processes will
416
 *    be blocked by the condition variable until the leader wakes them up.
417
 * ---------------
418
 */
419
static bool
420
BitmapShouldInitializeSharedState(ParallelBitmapHeapState *pstate)
421
0
{
422
0
  SharedBitmapState state;
423
424
0
  while (1)
425
0
  {
426
0
    SpinLockAcquire(&pstate->mutex);
427
0
    state = pstate->state;
428
0
    if (pstate->state == BM_INITIAL)
429
0
      pstate->state = BM_INPROGRESS;
430
0
    SpinLockRelease(&pstate->mutex);
431
432
    /* Exit if bitmap is done, or if we're the leader. */
433
0
    if (state != BM_INPROGRESS)
434
0
      break;
435
436
    /* Wait for the leader to wake us up. */
437
0
    ConditionVariableSleep(&pstate->cv, WAIT_EVENT_PARALLEL_BITMAP_SCAN);
438
0
  }
439
440
0
  ConditionVariableCancelSleep();
441
442
0
  return (state == BM_INITIAL);
443
0
}
444
445
/* ----------------------------------------------------------------
446
 *    ExecBitmapHeapEstimate
447
 *
448
 *    Compute the amount of space we'll need in the parallel
449
 *    query DSM, and inform pcxt->estimator about our needs.
450
 * ----------------------------------------------------------------
451
 */
452
void
453
ExecBitmapHeapEstimate(BitmapHeapScanState *node,
454
             ParallelContext *pcxt)
455
0
{
456
0
  Size    size;
457
458
0
  size = MAXALIGN(sizeof(ParallelBitmapHeapState));
459
460
  /* account for instrumentation, if required */
461
0
  if (node->ss.ps.instrument && pcxt->nworkers > 0)
462
0
  {
463
0
    size = add_size(size, offsetof(SharedBitmapHeapInstrumentation, sinstrument));
464
0
    size = add_size(size, mul_size(pcxt->nworkers, sizeof(BitmapHeapScanInstrumentation)));
465
0
  }
466
467
0
  shm_toc_estimate_chunk(&pcxt->estimator, size);
468
0
  shm_toc_estimate_keys(&pcxt->estimator, 1);
469
0
}
470
471
/* ----------------------------------------------------------------
472
 *    ExecBitmapHeapInitializeDSM
473
 *
474
 *    Set up a parallel bitmap heap scan descriptor.
475
 * ----------------------------------------------------------------
476
 */
477
void
478
ExecBitmapHeapInitializeDSM(BitmapHeapScanState *node,
479
              ParallelContext *pcxt)
480
0
{
481
0
  ParallelBitmapHeapState *pstate;
482
0
  SharedBitmapHeapInstrumentation *sinstrument = NULL;
483
0
  dsa_area   *dsa = node->ss.ps.state->es_query_dsa;
484
0
  char     *ptr;
485
0
  Size    size;
486
487
  /* If there's no DSA, there are no workers; initialize nothing. */
488
0
  if (dsa == NULL)
489
0
    return;
490
491
0
  size = MAXALIGN(sizeof(ParallelBitmapHeapState));
492
0
  if (node->ss.ps.instrument && pcxt->nworkers > 0)
493
0
  {
494
0
    size = add_size(size, offsetof(SharedBitmapHeapInstrumentation, sinstrument));
495
0
    size = add_size(size, mul_size(pcxt->nworkers, sizeof(BitmapHeapScanInstrumentation)));
496
0
  }
497
498
0
  ptr = shm_toc_allocate(pcxt->toc, size);
499
0
  pstate = (ParallelBitmapHeapState *) ptr;
500
0
  ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
501
0
  if (node->ss.ps.instrument && pcxt->nworkers > 0)
502
0
    sinstrument = (SharedBitmapHeapInstrumentation *) ptr;
503
504
0
  pstate->tbmiterator = 0;
505
506
  /* Initialize the mutex */
507
0
  SpinLockInit(&pstate->mutex);
508
0
  pstate->state = BM_INITIAL;
509
510
0
  ConditionVariableInit(&pstate->cv);
511
512
0
  if (sinstrument)
513
0
  {
514
0
    sinstrument->num_workers = pcxt->nworkers;
515
516
    /* ensure any unfilled slots will contain zeroes */
517
0
    memset(sinstrument->sinstrument, 0,
518
0
         pcxt->nworkers * sizeof(BitmapHeapScanInstrumentation));
519
0
  }
520
521
0
  shm_toc_insert(pcxt->toc, node->ss.ps.plan->plan_node_id, pstate);
522
0
  node->pstate = pstate;
523
0
  node->sinstrument = sinstrument;
524
0
}
525
526
/* ----------------------------------------------------------------
527
 *    ExecBitmapHeapReInitializeDSM
528
 *
529
 *    Reset shared state before beginning a fresh scan.
530
 * ----------------------------------------------------------------
531
 */
532
void
533
ExecBitmapHeapReInitializeDSM(BitmapHeapScanState *node,
534
                ParallelContext *pcxt)
535
0
{
536
0
  ParallelBitmapHeapState *pstate = node->pstate;
537
0
  dsa_area   *dsa = node->ss.ps.state->es_query_dsa;
538
539
  /* If there's no DSA, there are no workers; do nothing. */
540
0
  if (dsa == NULL)
541
0
    return;
542
543
0
  pstate->state = BM_INITIAL;
544
545
0
  if (DsaPointerIsValid(pstate->tbmiterator))
546
0
    tbm_free_shared_area(dsa, pstate->tbmiterator);
547
548
0
  pstate->tbmiterator = InvalidDsaPointer;
549
0
}
550
551
/* ----------------------------------------------------------------
552
 *    ExecBitmapHeapInitializeWorker
553
 *
554
 *    Copy relevant information from TOC into planstate.
555
 * ----------------------------------------------------------------
556
 */
557
void
558
ExecBitmapHeapInitializeWorker(BitmapHeapScanState *node,
559
                 ParallelWorkerContext *pwcxt)
560
0
{
561
0
  char     *ptr;
562
563
0
  Assert(node->ss.ps.state->es_query_dsa != NULL);
564
565
0
  ptr = shm_toc_lookup(pwcxt->toc, node->ss.ps.plan->plan_node_id, false);
566
567
0
  node->pstate = (ParallelBitmapHeapState *) ptr;
568
0
  ptr += MAXALIGN(sizeof(ParallelBitmapHeapState));
569
570
0
  if (node->ss.ps.instrument)
571
0
    node->sinstrument = (SharedBitmapHeapInstrumentation *) ptr;
572
0
}
573
574
/* ----------------------------------------------------------------
575
 *    ExecBitmapHeapRetrieveInstrumentation
576
 *
577
 *    Transfer bitmap heap scan statistics from DSM to private memory.
578
 * ----------------------------------------------------------------
579
 */
580
void
581
ExecBitmapHeapRetrieveInstrumentation(BitmapHeapScanState *node)
582
0
{
583
0
  SharedBitmapHeapInstrumentation *sinstrument = node->sinstrument;
584
0
  Size    size;
585
586
0
  if (sinstrument == NULL)
587
0
    return;
588
589
0
  size = offsetof(SharedBitmapHeapInstrumentation, sinstrument)
590
0
    + sinstrument->num_workers * sizeof(BitmapHeapScanInstrumentation);
591
592
0
  node->sinstrument = palloc(size);
593
0
  memcpy(node->sinstrument, sinstrument, size);
594
0
}