Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/executor/nodeSamplescan.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeSamplescan.c
4
 *    Support routines for sample scans of relations (table sampling).
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/nodeSamplescan.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/relscan.h"
18
#include "access/tableam.h"
19
#include "access/tsmapi.h"
20
#include "common/pg_prng.h"
21
#include "executor/executor.h"
22
#include "executor/nodeSamplescan.h"
23
#include "utils/fmgrprotos.h"
24
#include "utils/rel.h"
25
26
static TupleTableSlot *SampleNext(SampleScanState *node);
27
static void tablesample_init(SampleScanState *scanstate);
28
static TupleTableSlot *tablesample_getnext(SampleScanState *scanstate);
29
30
/* ----------------------------------------------------------------
31
 *            Scan Support
32
 * ----------------------------------------------------------------
33
 */
34
35
/* ----------------------------------------------------------------
36
 *    SampleNext
37
 *
38
 *    This is a workhorse for ExecSampleScan
39
 * ----------------------------------------------------------------
40
 */
41
static TupleTableSlot *
42
SampleNext(SampleScanState *node)
43
0
{
44
  /*
45
   * if this is first call within a scan, initialize
46
   */
47
0
  if (!node->begun)
48
0
    tablesample_init(node);
49
50
  /*
51
   * get the next tuple, and store it in our result slot
52
   */
53
0
  return tablesample_getnext(node);
54
0
}
55
56
/*
57
 * SampleRecheck -- access method routine to recheck a tuple in EvalPlanQual
58
 */
59
static bool
60
SampleRecheck(SampleScanState *node, TupleTableSlot *slot)
61
0
{
62
  /*
63
   * No need to recheck for SampleScan, since like SeqScan we don't pass any
64
   * checkable keys to heap_beginscan.
65
   */
66
0
  return true;
67
0
}
68
69
/* ----------------------------------------------------------------
70
 *    ExecSampleScan(node)
71
 *
72
 *    Scans the relation using the sampling method and returns
73
 *    the next qualifying tuple.
74
 *    We call the ExecScan() routine and pass it the appropriate
75
 *    access method functions.
76
 * ----------------------------------------------------------------
77
 */
78
static TupleTableSlot *
79
ExecSampleScan(PlanState *pstate)
80
0
{
81
0
  SampleScanState *node = castNode(SampleScanState, pstate);
82
83
0
  return ExecScan(&node->ss,
84
0
          (ExecScanAccessMtd) SampleNext,
85
0
          (ExecScanRecheckMtd) SampleRecheck);
86
0
}
87
88
/* ----------------------------------------------------------------
89
 *    ExecInitSampleScan
90
 * ----------------------------------------------------------------
91
 */
92
SampleScanState *
93
ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
94
0
{
95
0
  SampleScanState *scanstate;
96
0
  TableSampleClause *tsc = node->tablesample;
97
0
  TsmRoutine *tsm;
98
99
0
  Assert(outerPlan(node) == NULL);
100
0
  Assert(innerPlan(node) == NULL);
101
102
  /*
103
   * create state structure
104
   */
105
0
  scanstate = makeNode(SampleScanState);
106
0
  scanstate->ss.ps.plan = (Plan *) node;
107
0
  scanstate->ss.ps.state = estate;
108
0
  scanstate->ss.ps.ExecProcNode = ExecSampleScan;
109
110
  /*
111
   * Miscellaneous initialization
112
   *
113
   * create expression context for node
114
   */
115
0
  ExecAssignExprContext(estate, &scanstate->ss.ps);
116
117
  /*
118
   * open the scan relation
119
   */
120
0
  scanstate->ss.ss_currentRelation =
121
0
    ExecOpenScanRelation(estate,
122
0
               node->scan.scanrelid,
123
0
               eflags);
124
125
  /* we won't set up the HeapScanDesc till later */
126
0
  scanstate->ss.ss_currentScanDesc = NULL;
127
128
  /* and create slot with appropriate rowtype */
129
0
  ExecInitScanTupleSlot(estate, &scanstate->ss,
130
0
              RelationGetDescr(scanstate->ss.ss_currentRelation),
131
0
              table_slot_callbacks(scanstate->ss.ss_currentRelation),
132
0
              TTS_FLAG_OBEYS_NOT_NULL_CONSTRAINTS);
133
134
  /*
135
   * Initialize result type and projection.
136
   */
137
0
  ExecInitResultTypeTL(&scanstate->ss.ps);
138
0
  ExecAssignScanProjectionInfo(&scanstate->ss);
139
140
  /*
141
   * initialize child expressions
142
   */
143
0
  scanstate->ss.ps.qual =
144
0
    ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
145
146
0
  scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate);
147
0
  scanstate->repeatable =
148
0
    ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
149
150
  /*
151
   * If we don't have a REPEATABLE clause, select a random seed.  We want to
152
   * do this just once, since the seed shouldn't change over rescans.
153
   */
154
0
  if (tsc->repeatable == NULL)
155
0
    scanstate->seed = pg_prng_uint32(&pg_global_prng_state);
156
157
  /*
158
   * Finally, initialize the TABLESAMPLE method handler.
159
   */
160
0
  tsm = GetTsmRoutine(tsc->tsmhandler);
161
0
  scanstate->tsmroutine = tsm;
162
0
  scanstate->tsm_state = NULL;
163
164
0
  if (tsm->InitSampleScan)
165
0
    tsm->InitSampleScan(scanstate, eflags);
166
167
  /* We'll do BeginSampleScan later; we can't evaluate params yet */
168
0
  scanstate->begun = false;
169
170
0
  return scanstate;
171
0
}
172
173
/* ----------------------------------------------------------------
174
 *    ExecEndSampleScan
175
 *
176
 *    frees any storage allocated through C routines.
177
 * ----------------------------------------------------------------
178
 */
179
void
180
ExecEndSampleScan(SampleScanState *node)
181
0
{
182
  /*
183
   * Tell sampling function that we finished the scan.
184
   */
185
0
  if (node->tsmroutine->EndSampleScan)
186
0
    node->tsmroutine->EndSampleScan(node);
187
188
  /*
189
   * close heap scan
190
   */
191
0
  if (node->ss.ss_currentScanDesc)
192
0
    table_endscan(node->ss.ss_currentScanDesc);
193
0
}
194
195
/* ----------------------------------------------------------------
196
 *    ExecReScanSampleScan
197
 *
198
 *    Rescans the relation.
199
 *
200
 * ----------------------------------------------------------------
201
 */
202
void
203
ExecReScanSampleScan(SampleScanState *node)
204
0
{
205
  /* Remember we need to do BeginSampleScan again (if we did it at all) */
206
0
  node->begun = false;
207
0
  node->done = false;
208
0
  node->haveblock = false;
209
0
  node->donetuples = 0;
210
211
0
  ExecScanReScan(&node->ss);
212
0
}
213
214
215
/*
216
 * Initialize the TABLESAMPLE method: evaluate params and call BeginSampleScan.
217
 */
218
static void
219
tablesample_init(SampleScanState *scanstate)
220
0
{
221
0
  TsmRoutine *tsm = scanstate->tsmroutine;
222
0
  ExprContext *econtext = scanstate->ss.ps.ps_ExprContext;
223
0
  Datum    *params;
224
0
  Datum   datum;
225
0
  bool    isnull;
226
0
  uint32    seed;
227
0
  bool    allow_sync;
228
0
  int     i;
229
0
  ListCell   *arg;
230
231
0
  scanstate->donetuples = 0;
232
0
  params = palloc_array(Datum, list_length(scanstate->args));
233
234
0
  i = 0;
235
0
  foreach(arg, scanstate->args)
236
0
  {
237
0
    ExprState  *argstate = (ExprState *) lfirst(arg);
238
239
0
    params[i] = ExecEvalExprSwitchContext(argstate,
240
0
                        econtext,
241
0
                        &isnull);
242
0
    if (isnull)
243
0
      ereport(ERROR,
244
0
          (errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
245
0
           errmsg("TABLESAMPLE parameter cannot be null")));
246
0
    i++;
247
0
  }
248
249
0
  if (scanstate->repeatable)
250
0
  {
251
0
    datum = ExecEvalExprSwitchContext(scanstate->repeatable,
252
0
                      econtext,
253
0
                      &isnull);
254
0
    if (isnull)
255
0
      ereport(ERROR,
256
0
          (errcode(ERRCODE_INVALID_TABLESAMPLE_REPEAT),
257
0
           errmsg("TABLESAMPLE REPEATABLE parameter cannot be null")));
258
259
    /*
260
     * The REPEATABLE parameter has been coerced to float8 by the parser.
261
     * The reason for using float8 at the SQL level is that it will
262
     * produce unsurprising results both for users used to databases that
263
     * accept only integers in the REPEATABLE clause and for those who
264
     * might expect that REPEATABLE works like setseed() (a float in the
265
     * range from -1 to 1).
266
     *
267
     * We use hashfloat8() to convert the supplied value into a suitable
268
     * seed.  For regression-testing purposes, that has the convenient
269
     * property that REPEATABLE(0) gives a machine-independent result.
270
     */
271
0
    seed = DatumGetUInt32(DirectFunctionCall1(hashfloat8, datum));
272
0
  }
273
0
  else
274
0
  {
275
    /* Use the seed selected by ExecInitSampleScan */
276
0
    seed = scanstate->seed;
277
0
  }
278
279
  /* Set default values for params that BeginSampleScan can adjust */
280
0
  scanstate->use_bulkread = true;
281
0
  scanstate->use_pagemode = true;
282
283
  /* Let tablesample method do its thing */
284
0
  tsm->BeginSampleScan(scanstate,
285
0
             params,
286
0
             list_length(scanstate->args),
287
0
             seed);
288
289
  /* We'll use syncscan if there's no NextSampleBlock function */
290
0
  allow_sync = (tsm->NextSampleBlock == NULL);
291
292
  /* Now we can create or reset the HeapScanDesc */
293
0
  if (scanstate->ss.ss_currentScanDesc == NULL)
294
0
  {
295
0
    scanstate->ss.ss_currentScanDesc =
296
0
      table_beginscan_sampling(scanstate->ss.ss_currentRelation,
297
0
                   scanstate->ss.ps.state->es_snapshot,
298
0
                   0, NULL,
299
0
                   scanstate->use_bulkread,
300
0
                   allow_sync,
301
0
                   scanstate->use_pagemode,
302
0
                   ScanRelIsReadOnly(&scanstate->ss) ?
303
0
                   SO_HINT_REL_READ_ONLY : SO_NONE);
304
0
  }
305
0
  else
306
0
  {
307
0
    table_rescan_set_params(scanstate->ss.ss_currentScanDesc, NULL,
308
0
                scanstate->use_bulkread,
309
0
                allow_sync,
310
0
                scanstate->use_pagemode);
311
0
  }
312
313
0
  pfree(params);
314
315
  /* And we're initialized. */
316
0
  scanstate->begun = true;
317
0
}
318
319
/*
320
 * Get next tuple from TABLESAMPLE method.
321
 */
322
static TupleTableSlot *
323
tablesample_getnext(SampleScanState *scanstate)
324
0
{
325
0
  TableScanDesc scan = scanstate->ss.ss_currentScanDesc;
326
0
  TupleTableSlot *slot = scanstate->ss.ss_ScanTupleSlot;
327
328
0
  ExecClearTuple(slot);
329
330
0
  if (scanstate->done)
331
0
    return NULL;
332
333
0
  for (;;)
334
0
  {
335
0
    if (!scanstate->haveblock)
336
0
    {
337
0
      if (!table_scan_sample_next_block(scan, scanstate))
338
0
      {
339
0
        scanstate->haveblock = false;
340
0
        scanstate->done = true;
341
342
        /* exhausted relation */
343
0
        return NULL;
344
0
      }
345
346
0
      scanstate->haveblock = true;
347
0
    }
348
349
0
    if (!table_scan_sample_next_tuple(scan, scanstate, slot))
350
0
    {
351
      /*
352
       * If we get here, it means we've exhausted the items on this page
353
       * and it's time to move to the next.
354
       */
355
0
      scanstate->haveblock = false;
356
0
      continue;
357
0
    }
358
359
    /* Found visible tuple, return it. */
360
0
    break;
361
0
  }
362
363
0
  scanstate->donetuples++;
364
365
0
  return slot;
366
0
}