| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2026 Tiger Data, Inc. | ||
| 3 | * Licensed under the PostgreSQL License. See LICENSE for details. | ||
| 4 | * | ||
| 5 | * scan_bound.c - row target for a prism scan | ||
| 6 | * | ||
| 7 | * The index AM API never tells a scan how many rows the query wants: the | ||
| 8 | * executor pulls one tuple at a time until the Limit node above it is | ||
| 9 | * satisfied. A prism scan computes its whole top-k on the first fetch, | ||
| 10 | * so it has to know k up front or it emits a fixed default and a larger | ||
| 11 | * LIMIT silently comes up short. | ||
| 12 | * | ||
| 13 | * Knowing k up front is also what makes the rows it returns correctly | ||
| 14 | * ordered: the whole top-k is elected in one pass over the probed | ||
| 15 | * clusters, so every row is ranked against every candidate. A scan that | ||
| 16 | * instead resumed once its budget ran out could only rank the newcomers | ||
| 17 | * against each other, and might find one nearer than a row it has already | ||
| 18 | * returned -- ordering the caller cannot rely on. | ||
| 19 | * | ||
| 20 | * A scan therefore asks, at rescan, whether it runs under a Limit. The | ||
| 21 | * hook below only records which query is executing; the search runs | ||
| 22 | * inside the scan, where the executor node already points at it and can | ||
| 23 | * be matched by identity rather than by position in the plan tree. | ||
| 24 | * | ||
| 25 | * When the scan carries a filter (a WHERE clause the executor applies | ||
| 26 | * above the index), only a fraction of the emitted top-k survives it, so | ||
| 27 | * k is inflated by the planner's selectivity estimate. | ||
| 28 | */ | ||
| 29 | |||
| 30 | #include <postgres.h> | ||
| 31 | |||
| 32 | #include <access/genam.h> | ||
| 33 | #include <executor/executor.h> | ||
| 34 | #include <math.h> | ||
| 35 | #include <nodes/execnodes.h> | ||
| 36 | #include <nodes/nodeFuncs.h> | ||
| 37 | #include <nodes/plannodes.h> | ||
| 38 | #include <optimizer/optimizer.h> | ||
| 39 | #include <utils/rel.h> | ||
| 40 | |||
| 41 | #include "scan.h" | ||
| 42 | #include "scan_bound.h" | ||
| 43 | #include "support_pg.h" | ||
| 44 | |||
| 45 | static ExecutorRun_hook_type prev_ExecutorRun_hook = NULL; | ||
| 46 | |||
| 47 | /* | ||
| 48 | * The query we are executing inside, or NULL when no executor is running | ||
| 49 | * (maintenance code opening its own index scans, say). Saved and restored | ||
| 50 | * around the run, the way the executor maintains ActivePortal: nesting | ||
| 51 | * follows the C stack and names the innermost query, and an error unwinds | ||
| 52 | * this along with the executor. | ||
| 53 | */ | ||
| 54 | static QueryDesc *vs_active_query_desc = NULL; | ||
| 55 | |||
| 56 | /* | ||
| 57 | * True if the expression reads an executor parameter that has no value yet. | ||
| 58 | * | ||
| 59 | * A PARAM_EXEC is a parameter whose value another plan node produces during | ||
| 60 | * execution -- an InitPlan's result, or the current row of the outer | ||
| 61 | * relation in a nested loop -- as opposed to a PARAM_EXTERN, which the | ||
| 62 | * client binds before execution begins. Until the node that owes it has | ||
| 63 | * run, its slot carries the subplan to run rather than a value (execPlan is | ||
| 64 | * non-NULL), and evaluating it would execute that subplan from inside a | ||
| 65 | * rescan. Decline in that case. | ||
| 66 | * | ||
| 67 | * Params already produced are fine, which is what asking at rescan buys | ||
| 68 | * over asking at executor start. | ||
| 69 | */ | ||
| 70 | static bool | ||
| 71 | 289 | has_pending_exec_param(Node *node, void *context) | |
| 72 | { | ||
| 73 |
1/2✓ Branch 0 taken 289 times.
✗ Branch 1 not taken.
|
289 | if (node == NULL) |
| 74 | return false; | ||
| 75 | |||
| 76 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 279 times.
|
289 | if (IsA(node, Param)) |
| 77 | { | ||
| 78 | 10 | Param *p = (Param *)node; | |
| 79 | 10 | ExprContext *econtext = (ExprContext *)context; | |
| 80 | |||
| 81 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
|
10 | if (p->paramkind != PARAM_EXEC) |
| 82 | return false; | ||
| 83 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | return econtext == NULL || econtext->ecxt_param_exec_vals == NULL || |
| 84 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
24 | p->paramid < 0 || |
| 85 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | econtext->ecxt_param_exec_vals[p->paramid].execPlan != NULL; |
| 86 | } | ||
| 87 | 279 | return expression_tree_walker(node, has_pending_exec_param, context); | |
| 88 | } | ||
| 89 | |||
| 90 | /* | ||
| 91 | * Evaluate a LIMIT or OFFSET expression. | ||
| 92 | * | ||
| 93 | * OFFSET counts toward what the scan must produce: the Limit node discards | ||
| 94 | * the skipped rows only after the scan has emitted them, so LIMIT 10 OFFSET | ||
| 95 | * 100 needs 110 rows out of the index, not 10. | ||
| 96 | * | ||
| 97 | * Only values fixed for the rest of this scan qualify: constants, | ||
| 98 | * parameters already bound, and stable expressions over them. False when | ||
| 99 | * the value cannot be known here, or is NULL (which SQL reads as "no | ||
| 100 | * limit"). | ||
| 101 | */ | ||
| 102 | static bool | ||
| 103 | 279 | eval_count_expr( | |
| 104 | Node *expr, ExprState *state, ExprContext *econtext, int64 *value) | ||
| 105 | { | ||
| 106 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
|
279 | if (expr == NULL || state == NULL) |
| 107 | return false; | ||
| 108 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
|
279 | if (has_pending_exec_param(expr, econtext)) |
| 109 | return false; | ||
| 110 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
|
279 | if (contain_volatile_functions(expr)) |
| 111 | return false; | ||
| 112 | |||
| 113 | 279 | bool isnull; | |
| 114 | 279 | Datum d = ExecEvalExprSwitchContext(state, econtext, &isnull); | |
| 115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 279 times.
|
279 | if (isnull) |
| 116 | return false; | ||
| 117 | |||
| 118 | 279 | *value = DatumGetInt64(d); | |
| 119 | 279 | return *value >= 0; | |
| 120 | } | ||
| 121 | |||
| 122 | /* | ||
| 123 | * Descend from a Limit's input to the IndexScan feeding it, through nodes | ||
| 124 | * that emit exactly one row per input row. Anything that can filter, | ||
| 125 | * aggregate, sort or multiply rows breaks the correspondence between the | ||
| 126 | * LIMIT and the rows the scan must produce, so the descent stops there. | ||
| 127 | */ | ||
| 128 | static IndexScanState * | ||
| 129 | 281 | find_index_scan_state(PlanState *ps) | |
| 130 | { | ||
| 131 |
1/2✓ Branch 0 taken 281 times.
✗ Branch 1 not taken.
|
281 | while (ps != NULL) |
| 132 | { | ||
| 133 |
1/2✓ Branch 0 taken 281 times.
✗ Branch 1 not taken.
|
281 | if (IsA(ps, IndexScanState)) |
| 134 | 281 | return (IndexScanState *)ps; | |
| 135 | |||
| 136 | /* A qual on any node between here and the scan filters rows. */ | ||
| 137 | ✗ | if (ps->plan->qual != NIL) | |
| 138 | return NULL; | ||
| 139 | |||
| 140 | ✗ | switch (nodeTag(ps)) | |
| 141 | { | ||
| 142 | ✗ | case T_ResultState: | |
| 143 | ✗ | ps = outerPlanState(ps); | |
| 144 | ✗ | break; | |
| 145 | |||
| 146 | ✗ | case T_WindowAggState: | |
| 147 | ✗ | if (((WindowAgg *)ps->plan)->runCondition != NIL) | |
| 148 | return NULL; | ||
| 149 | ✗ | ps = outerPlanState(ps); | |
| 150 | ✗ | break; | |
| 151 | |||
| 152 | ✗ | case T_SubqueryScanState: | |
| 153 | ✗ | ps = ((SubqueryScanState *)ps)->subplan; | |
| 154 | ✗ | break; | |
| 155 | |||
| 156 | default: | ||
| 157 | return NULL; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | return NULL; | ||
| 161 | } | ||
| 162 | |||
| 163 | /* | ||
| 164 | * A LIMIT's count and offset are each clamped to this before being added, | ||
| 165 | * so the sum cannot overflow. Purely an arithmetic guard: what limits a | ||
| 166 | * scan is work_mem, applied where the top-k is allocated. | ||
| 167 | */ | ||
| 168 | #define PRISM_LIMIT_SUM_MAX (PG_INT64_MAX / 4) | ||
| 169 | |||
| 170 | /* | ||
| 171 | * Standard deviations of headroom over the rows a filter is expected to | ||
| 172 | * leave. | ||
| 173 | * | ||
| 174 | * A filter that passes a fraction s of the rows needs about 1/s candidates | ||
| 175 | * for s of them to add up to the LIMIT -- but that is a mean, not a | ||
| 176 | * guarantee: with a correct s, the survivors among the top-(k/s) are | ||
| 177 | * distributed Binomial(k/s, s), so the count has mean k and standard | ||
| 178 | * deviation about sqrt(k). Over-fetching by 1 + this/sqrt(k) covers that | ||
| 179 | * noise: roughly 1.95x at k = 10, 1.3x at k = 100, 1.09x at k = 1000. A flat | ||
| 180 | * factor would have to be sized for the smallest k and would then over-fetch | ||
| 181 | * by 1.5x to 2.7x at the larger ones, where the fetches cost most. | ||
| 182 | * | ||
| 183 | * This covers noise, not a wrong estimate. An s off by an order of magnitude | ||
| 184 | * needs an order of magnitude more candidates, which nothing here can | ||
| 185 | * anticipate -- raising work_mem, or a partial index on the filter, is the | ||
| 186 | * answer to that. | ||
| 187 | */ | ||
| 188 | #define PRISM_FILTER_NOISE_SIGMAS 3.0 | ||
| 189 | |||
| 190 | /* | ||
| 191 | * Smallest estimated selectivity the sizing will divide by. | ||
| 192 | * | ||
| 193 | * One row in ten thousand already asks for a top-k ten thousand times the | ||
| 194 | * LIMIT, which work_mem will cut down anyway. Anything below this is a | ||
| 195 | * planner estimate with no rows behind it -- a default from a missing | ||
| 196 | * statistic, or a conjunction of independent guesses -- and dividing by it | ||
| 197 | * produces a number, not an estimate. | ||
| 198 | */ | ||
| 199 | #define PRISM_FILTER_MIN_SELECTIVITY 1e-4 | ||
| 200 | |||
| 201 | /* | ||
| 202 | * Size the scan's top-k for the rows the LIMIT will pull. | ||
| 203 | * | ||
| 204 | * A filter on the scan is applied by the executor after the index has emitted | ||
| 205 | * its top-k, so only an estimated fraction s of the emitted rows survive: | ||
| 206 | * multiply the bound by 1/s, with a margin, so that fraction still covers | ||
| 207 | * the LIMIT. The estimate is the plan's own row | ||
| 208 | * count for the scan node against the relation's statistics; without | ||
| 209 | * statistics the plain LIMIT is used, and where the sizing exceeds what | ||
| 210 | * work_mem affords a filtered query comes up short. | ||
| 211 | * | ||
| 212 | * The estimate is only ever a guess -- a filter correlated with vector | ||
| 213 | * proximity (a category living in its own region of the space) can still | ||
| 214 | * starve the result, and nothing here can know which rows survive, because | ||
| 215 | * the pruning gate commits this size before any heap fetch and evaluating a | ||
| 216 | * qual needs one. prism.query_limit is the lever for a caller who knows their | ||
| 217 | * own selectivity. | ||
| 218 | */ | ||
| 219 | static uint32_t | ||
| 220 | 278 | size_top_k(IndexScanState *iss, int64 limit) | |
| 221 | { | ||
| 222 |
3/4✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 204 times.
✓ Branch 3 taken 74 times.
|
278 | uint32_t k = (uint32_t)Min(Max(limit, 1), (int64)PG_UINT32_MAX); |
| 223 | 278 | Plan *plan = iss->ss.ps.plan; | |
| 224 | 278 | Relation heap = iss->ss.ss_currentRelation; | |
| 225 | |||
| 226 |
4/6✓ Branch 0 taken 6 times.
✓ Branch 1 taken 272 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
278 | if (plan->qual == NIL || heap == NULL || heap->rd_rel->reltuples <= 0.0) |
| 227 | return k; | ||
| 228 | |||
| 229 | /* | ||
| 230 | * A row estimate is a planner guess divided by a cached count, so it can | ||
| 231 | * arrive at anything: zero rows from a qual the planner reads as | ||
| 232 | * impossible, more rows than the relation is recorded as holding after a | ||
| 233 | * bulk load, a non-finite value from either being garbage. Only a | ||
| 234 | * fraction strictly inside (0, 1) says anything about filtering; take | ||
| 235 | * the LIMIT unchanged for the rest rather than dividing by them. | ||
| 236 | * | ||
| 237 | * PRISM_FILTER_MIN_SELECTIVITY floors the divisor. Without it a plan_rows | ||
| 238 | * of a millionth of a row asks for a top-k a million times the LIMIT, | ||
| 239 | * and while work_mem would refuse to allocate it, the sizing has no | ||
| 240 | * business proposing it: below this the estimate is noise, not a | ||
| 241 | * measurement. | ||
| 242 | */ | ||
| 243 | 6 | return prism_scan_inflate_for_filter( | |
| 244 | 6 | k, plan->plan_rows / heap->rd_rel->reltuples); | |
| 245 | } | ||
| 246 | |||
| 247 | /* | ||
| 248 | * Inflate a row target for a filter the executor applies above the scan. | ||
| 249 | * | ||
| 250 | * Called by the executor and by the cost model. The executor arrives at the | ||
| 251 | * selectivity by dividing its own plan_rows by the relation's row count; | ||
| 252 | * the planner passes clauselist_selectivity's fraction for the same thing. | ||
| 253 | */ | ||
| 254 | uint32_t | ||
| 255 | 388 | prism_scan_inflate_for_filter(uint32_t k, double selectivity) | |
| 256 | { | ||
| 257 |
2/2✓ Branch 0 taken 384 times.
✓ Branch 1 taken 4 times.
|
388 | if (k == 0) |
| 258 | return 0; | ||
| 259 | |||
| 260 | /* | ||
| 261 | * A row estimate is a planner guess divided by a cached count, so it can | ||
| 262 | * arrive at anything: zero rows from a qual the planner reads as | ||
| 263 | * impossible, more rows than the relation is recorded as holding after a | ||
| 264 | * bulk load, a non-finite value from either being garbage. Only a | ||
| 265 | * fraction strictly inside (0, 1) says anything about filtering; take | ||
| 266 | * the target unchanged for the rest rather than dividing by them. | ||
| 267 | * | ||
| 268 | * PRISM_FILTER_MIN_SELECTIVITY floors the divisor. Without it a plan_rows | ||
| 269 | * of a millionth of a row asks for a top-k a million times the target, | ||
| 270 | * and while work_mem would refuse to allocate it, the sizing has no | ||
| 271 | * business proposing it: below this the estimate is noise, not a | ||
| 272 | * measurement. | ||
| 273 | */ | ||
| 274 |
3/4✓ Branch 0 taken 384 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 371 times.
✓ Branch 3 taken 13 times.
|
384 | if (!isfinite(selectivity) || selectivity >= 1.0 || selectivity <= 0.0) |
| 275 | return k; | ||
| 276 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
|
13 | if (selectivity < PRISM_FILTER_MIN_SELECTIVITY) |
| 277 | ✗ | selectivity = PRISM_FILTER_MIN_SELECTIVITY; | |
| 278 | |||
| 279 | 13 | double margin = 1.0 + PRISM_FILTER_NOISE_SIGMAS / sqrt((double)k); | |
| 280 | 13 | double sized = ceil(margin * (double)k / selectivity); | |
| 281 | |||
| 282 | /* | ||
| 283 | * Clamped only to keep the cast well defined. The ceiling that matters | ||
| 284 | * is work_mem, applied by prism_scan_resolve_top_k. | ||
| 285 | */ | ||
| 286 |
3/6✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 13 times.
|
13 | return (uint32_t)Min(Max(sized, (double)k), (double)PG_UINT32_MAX); |
| 287 | } | ||
| 288 | |||
| 289 | /* | ||
| 290 | * Rows a Limit will pull: its count plus its offset, each clamped so the | ||
| 291 | * sum cannot overflow. False when either cannot be resolved now, when the | ||
| 292 | * count is zero, or under WITH TIES -- which keeps pulling past the count | ||
| 293 | * for rows tying the last one, so the count is a floor on the rows needed | ||
| 294 | * rather than the number of them, and a scan is better left at its default | ||
| 295 | * sizing than cut ties off. | ||
| 296 | */ | ||
| 297 | static bool | ||
| 298 | 278 | limit_total_rows(LimitState *ls, int64 *total) | |
| 299 | { | ||
| 300 | 278 | Limit *plan = (Limit *)ls->ps.plan; | |
| 301 | 278 | ExprContext *econtext = ls->ps.ps_ExprContext; | |
| 302 | 278 | int64 count = 0; | |
| 303 | 278 | int64 offset = 0; | |
| 304 | |||
| 305 | /* | ||
| 306 | * WITH TIES pulls past its count for rows tying the last one, so the | ||
| 307 | * count is a floor rather than the total -- but it is the right thing to | ||
| 308 | * size from. Reporting no bound would have resolve_top_k read the query | ||
| 309 | * as asking for every row, and size FETCH FIRST 5 ROWS WITH TIES for the | ||
| 310 | * whole relation. A tie group straddling the top-k's edge can be cut | ||
| 311 | * short instead, which is the same bound every other query is subject to | ||
| 312 | * and vastly cheaper than ranking the table. | ||
| 313 | */ | ||
| 314 |
1/2✓ Branch 1 taken 278 times.
✗ Branch 2 not taken.
|
278 | if (!eval_count_expr(plan->limitCount, ls->limitCount, econtext, &count) || |
| 315 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 278 times.
|
278 | count <= 0) |
| 316 | return false; | ||
| 317 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 277 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
279 | if (plan->limitOffset != NULL && |
| 318 | 1 | !eval_count_expr( | |
| 319 | plan->limitOffset, ls->limitOffset, econtext, &offset)) | ||
| 320 | return false; | ||
| 321 | |||
| 322 | 278 | *total = Min(count, PRISM_LIMIT_SUM_MAX) + | |
| 323 | 278 | Min(offset, PRISM_LIMIT_SUM_MAX); | |
| 324 | 278 | return true; | |
| 325 | } | ||
| 326 | |||
| 327 | /* The scan looking for its Limit; k stays 0 until one is resolved. */ | ||
| 328 | typedef struct LimitSearch | ||
| 329 | { | ||
| 330 | IndexScanDesc scan; | ||
| 331 | uint32_t k; | ||
| 332 | } LimitSearch; | ||
| 333 | |||
| 334 | /* | ||
| 335 | * Look for a Limit whose input is the asking scan. The pairing is by | ||
| 336 | * identity -- nodeIndexscan assigns iss_ScanDesc before calling | ||
| 337 | * index_rescan -- which is what tells two scans of the same index in one | ||
| 338 | * statement apart, where their position in the plan tree cannot. | ||
| 339 | */ | ||
| 340 | static bool | ||
| 341 | 670 | limit_walker(PlanState *ps, void *context) | |
| 342 | { | ||
| 343 | 670 | LimitSearch *search = (LimitSearch *)context; | |
| 344 | |||
| 345 |
1/2✓ Branch 0 taken 670 times.
✗ Branch 1 not taken.
|
670 | if (ps == NULL) |
| 346 | return false; | ||
| 347 | |||
| 348 |
2/2✓ Branch 0 taken 281 times.
✓ Branch 1 taken 389 times.
|
670 | if (IsA(ps, LimitState)) |
| 349 | { | ||
| 350 | 281 | IndexScanState *iss = find_index_scan_state(outerPlanState(ps)); | |
| 351 | 281 | int64 total; | |
| 352 | |||
| 353 |
3/4✓ Branch 0 taken 281 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✓ Branch 3 taken 3 times.
|
281 | if (iss != NULL && iss->iss_ScanDesc == search->scan && |
| 354 |
2/4✓ Branch 0 taken 278 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 278 times.
✗ Branch 3 not taken.
|
556 | iss->iss_NumOrderByKeys > 0 && |
| 355 | 278 | limit_total_rows((LimitState *)ps, &total)) | |
| 356 | { | ||
| 357 | 278 | search->k = size_top_k(iss, total); | |
| 358 | 278 | return true; /* this scan's Limit is resolved */ | |
| 359 | } | ||
| 360 | } | ||
| 361 | |||
| 362 | 392 | return planstate_tree_walker(ps, limit_walker, context); | |
| 363 | } | ||
| 364 | |||
| 365 | uint32_t | ||
| 366 | 282 | prism_scan_bound(IndexScanDesc scan) | |
| 367 | { | ||
| 368 | 282 | QueryDesc *qd = vs_active_query_desc; | |
| 369 | |||
| 370 |
3/6✓ Branch 0 taken 282 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 282 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 282 times.
✗ Branch 5 not taken.
|
282 | if (qd == NULL || qd->planstate == NULL || qd->estate == NULL) |
| 371 | return 0; /* no executor above us: keep the default sizing */ | ||
| 372 | |||
| 373 | 282 | LimitSearch search = {.scan = scan, .k = 0}; | |
| 374 | |||
| 375 | /* | ||
| 376 | * The main tree, then the plan trees hanging off the EState: CTE | ||
| 377 | * bodies, InitPlans and correlated subplans are not reachable from the | ||
| 378 | * root, and a CTE with its own ORDER BY ... LIMIT is the hybrid-search | ||
| 379 | * shape this exists for. | ||
| 380 | */ | ||
| 381 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 278 times.
|
282 | if (!limit_walker(qd->planstate, &search)) |
| 382 | { | ||
| 383 | 4 | ListCell *lc; | |
| 384 | |||
| 385 |
3/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
|
8 | foreach (lc, qd->estate->es_subplanstates) |
| 386 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | if (limit_walker((PlanState *)lfirst(lc), &search)) |
| 387 | break; | ||
| 388 | } | ||
| 389 | |||
| 390 | 282 | return search.k; | |
| 391 | } | ||
| 392 | |||
| 393 | /* | ||
| 394 | * Record which query is executing, for the duration of its execution. | ||
| 395 | * | ||
| 396 | * An index AM's callbacks are handed a Relation and an IndexScanDesc and | ||
| 397 | * nothing else -- there is no path from a scan back to the executor state | ||
| 398 | * it runs under. Noting the running query here is what lets prism_scan_bound | ||
| 399 | * find the plan tree at rescan and, in it, the Limit above this scan. | ||
| 400 | * | ||
| 401 | * Save, set, restore, exactly as the executor does for ActivePortal. | ||
| 402 | * PG_FINALLY does the restoring so that an error -- which leaves this frame | ||
| 403 | * by longjmp, not by return -- cannot strand a pointer to a QueryDesc whose | ||
| 404 | * memory has gone. Nesting needs no bookkeeping: a query issued from a | ||
| 405 | * function body runs its executor inside its caller's, so the C stack keeps | ||
| 406 | * the saves in order and the variable always names the innermost query. | ||
| 407 | */ | ||
| 408 | static void | ||
| 409 | 2190 | prism_executor_run(QueryDesc *queryDesc, ScanDirection direction, uint64 count) | |
| 410 | { | ||
| 411 | 2190 | QueryDesc *save = vs_active_query_desc; | |
| 412 | |||
| 413 | 2190 | vs_active_query_desc = queryDesc; | |
| 414 |
2/2✓ Branch 0 taken 2190 times.
✓ Branch 1 taken 9 times.
|
2199 | PG_TRY(); |
| 415 | { | ||
| 416 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2190 times.
|
2190 | if (prev_ExecutorRun_hook) |
| 417 | ✗ | prev_ExecutorRun_hook(queryDesc, direction, count); | |
| 418 | else | ||
| 419 | 2190 | standard_ExecutorRun(queryDesc, direction, count); | |
| 420 | } | ||
| 421 | 2190 | PG_FINALLY(); | |
| 422 | { | ||
| 423 | 2190 | vs_active_query_desc = save; | |
| 424 | } | ||
| 425 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2181 times.
|
2190 | PG_END_TRY(); |
| 426 | 2181 | } | |
| 427 | |||
| 428 | void | ||
| 429 | 255 | prism_scan_bound_init(void) | |
| 430 | { | ||
| 431 | 255 | prev_ExecutorRun_hook = ExecutorRun_hook; | |
| 432 | 255 | ExecutorRun_hook = prism_executor_run; | |
| 433 | 255 | } | |
| 434 |