/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 | } |