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/nodeLimit.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * nodeLimit.c
4
 *    Routines to handle limiting of query results where appropriate
5
 *
6
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/executor/nodeLimit.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
/*
16
 * INTERFACE ROUTINES
17
 *    ExecLimit   - extract a limited range of tuples
18
 *    ExecInitLimit - initialize node and subnodes..
19
 *    ExecEndLimit  - shutdown node and subnodes
20
 */
21
22
#include "postgres.h"
23
24
#include "executor/executor.h"
25
#include "executor/nodeLimit.h"
26
#include "miscadmin.h"
27
28
static void recompute_limits(LimitState *node);
29
static int64 compute_tuples_needed(LimitState *node);
30
31
32
/* ----------------------------------------------------------------
33
 *    ExecLimit
34
 *
35
 *    This is a very simple node which just performs LIMIT/OFFSET
36
 *    filtering on the stream of tuples returned by a subplan.
37
 * ----------------------------------------------------------------
38
 */
39
static TupleTableSlot *     /* return: a tuple or NULL */
40
ExecLimit(PlanState *pstate)
41
0
{
42
0
  LimitState *node = castNode(LimitState, pstate);
43
0
  ExprContext *econtext = node->ps.ps_ExprContext;
44
0
  ScanDirection direction;
45
0
  TupleTableSlot *slot;
46
0
  PlanState  *outerPlan;
47
48
0
  CHECK_FOR_INTERRUPTS();
49
50
  /*
51
   * get information from the node
52
   */
53
0
  direction = node->ps.state->es_direction;
54
0
  outerPlan = outerPlanState(node);
55
56
  /*
57
   * The main logic is a simple state machine.
58
   */
59
0
  switch (node->lstate)
60
0
  {
61
0
    case LIMIT_INITIAL:
62
63
      /*
64
       * First call for this node, so compute limit/offset. (We can't do
65
       * this any earlier, because parameters from upper nodes will not
66
       * be set during ExecInitLimit.)  This also sets position = 0 and
67
       * changes the state to LIMIT_RESCAN.
68
       */
69
0
      recompute_limits(node);
70
71
      /* FALL THRU */
72
73
0
    case LIMIT_RESCAN:
74
75
      /*
76
       * If backwards scan, just return NULL without changing state.
77
       */
78
0
      if (!ScanDirectionIsForward(direction))
79
0
        return NULL;
80
81
      /*
82
       * Check for empty window; if so, treat like empty subplan.
83
       */
84
0
      if (node->count <= 0 && !node->noCount)
85
0
      {
86
0
        node->lstate = LIMIT_EMPTY;
87
0
        return NULL;
88
0
      }
89
90
      /*
91
       * Fetch rows from subplan until we reach position > offset.
92
       */
93
0
      for (;;)
94
0
      {
95
0
        slot = ExecProcNode(outerPlan);
96
0
        if (TupIsNull(slot))
97
0
        {
98
          /*
99
           * The subplan returns too few tuples for us to produce
100
           * any output at all.
101
           */
102
0
          node->lstate = LIMIT_EMPTY;
103
0
          return NULL;
104
0
        }
105
106
        /*
107
         * Tuple at limit is needed for comparison in subsequent
108
         * execution to detect ties.
109
         */
110
0
        if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
111
0
          node->position - node->offset == node->count - 1)
112
0
        {
113
0
          ExecCopySlot(node->last_slot, slot);
114
0
        }
115
0
        node->subSlot = slot;
116
0
        if (++node->position > node->offset)
117
0
          break;
118
0
      }
119
120
      /*
121
       * Okay, we have the first tuple of the window.
122
       */
123
0
      node->lstate = LIMIT_INWINDOW;
124
0
      break;
125
126
0
    case LIMIT_EMPTY:
127
128
      /*
129
       * The subplan is known to return no tuples (or not more than
130
       * OFFSET tuples, in general).  So we return no tuples.
131
       */
132
0
      return NULL;
133
134
0
    case LIMIT_INWINDOW:
135
0
      if (ScanDirectionIsForward(direction))
136
0
      {
137
        /*
138
         * Forwards scan, so check for stepping off end of window.  At
139
         * the end of the window, the behavior depends on whether WITH
140
         * TIES was specified: if so, we need to change the state
141
         * machine to WINDOWEND_TIES, and fall through to the code for
142
         * that case.  If not (nothing was specified, or ONLY was)
143
         * return NULL without advancing the subplan or the position
144
         * variable, but change the state machine to record having
145
         * done so.
146
         *
147
         * Once at the end, ideally, we would shut down parallel
148
         * resources; but that would destroy the parallel context
149
         * which might be required for rescans.  To do that, we'll
150
         * need to find a way to pass down more information about
151
         * whether rescans are possible.
152
         */
153
0
        if (!node->noCount &&
154
0
          node->position - node->offset >= node->count)
155
0
        {
156
0
          if (node->limitOption == LIMIT_OPTION_COUNT)
157
0
          {
158
0
            node->lstate = LIMIT_WINDOWEND;
159
0
            return NULL;
160
0
          }
161
0
          else
162
0
          {
163
0
            node->lstate = LIMIT_WINDOWEND_TIES;
164
            /* we'll fall through to the next case */
165
0
          }
166
0
        }
167
0
        else
168
0
        {
169
          /*
170
           * Get next tuple from subplan, if any.
171
           */
172
0
          slot = ExecProcNode(outerPlan);
173
0
          if (TupIsNull(slot))
174
0
          {
175
0
            node->lstate = LIMIT_SUBPLANEOF;
176
0
            return NULL;
177
0
          }
178
179
          /*
180
           * If WITH TIES is active, and this is the last in-window
181
           * tuple, save it to be used in subsequent WINDOWEND_TIES
182
           * processing.
183
           */
184
0
          if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
185
0
            node->position - node->offset == node->count - 1)
186
0
          {
187
0
            ExecCopySlot(node->last_slot, slot);
188
0
          }
189
0
          node->subSlot = slot;
190
0
          node->position++;
191
0
          break;
192
0
        }
193
0
      }
194
0
      else
195
0
      {
196
        /*
197
         * Backwards scan, so check for stepping off start of window.
198
         * As above, only change state-machine status if so.
199
         */
200
0
        if (node->position <= node->offset + 1)
201
0
        {
202
0
          node->lstate = LIMIT_WINDOWSTART;
203
0
          return NULL;
204
0
        }
205
206
        /*
207
         * Get previous tuple from subplan; there should be one!
208
         */
209
0
        slot = ExecProcNode(outerPlan);
210
0
        if (TupIsNull(slot))
211
0
          elog(ERROR, "LIMIT subplan failed to run backwards");
212
0
        node->subSlot = slot;
213
0
        node->position--;
214
0
        break;
215
0
      }
216
217
0
      Assert(node->lstate == LIMIT_WINDOWEND_TIES);
218
      /* FALL THRU */
219
220
0
    case LIMIT_WINDOWEND_TIES:
221
0
      if (ScanDirectionIsForward(direction))
222
0
      {
223
        /*
224
         * Advance the subplan until we find the first row with
225
         * different ORDER BY pathkeys.
226
         */
227
0
        slot = ExecProcNode(outerPlan);
228
0
        if (TupIsNull(slot))
229
0
        {
230
0
          node->lstate = LIMIT_SUBPLANEOF;
231
0
          return NULL;
232
0
        }
233
234
        /*
235
         * Test if the new tuple and the last tuple match. If so we
236
         * return the tuple.
237
         */
238
0
        econtext->ecxt_innertuple = slot;
239
0
        econtext->ecxt_outertuple = node->last_slot;
240
0
        if (ExecQualAndReset(node->eqfunction, econtext))
241
0
        {
242
0
          node->subSlot = slot;
243
0
          node->position++;
244
0
        }
245
0
        else
246
0
        {
247
0
          node->lstate = LIMIT_WINDOWEND;
248
0
          return NULL;
249
0
        }
250
0
      }
251
0
      else
252
0
      {
253
        /*
254
         * Backwards scan, so check for stepping off start of window.
255
         * Change only state-machine status if so.
256
         */
257
0
        if (node->position <= node->offset + 1)
258
0
        {
259
0
          node->lstate = LIMIT_WINDOWSTART;
260
0
          return NULL;
261
0
        }
262
263
        /*
264
         * Get previous tuple from subplan; there should be one! And
265
         * change state-machine status.
266
         */
267
0
        slot = ExecProcNode(outerPlan);
268
0
        if (TupIsNull(slot))
269
0
          elog(ERROR, "LIMIT subplan failed to run backwards");
270
0
        node->subSlot = slot;
271
0
        node->position--;
272
0
        node->lstate = LIMIT_INWINDOW;
273
0
      }
274
0
      break;
275
276
0
    case LIMIT_SUBPLANEOF:
277
0
      if (ScanDirectionIsForward(direction))
278
0
        return NULL;
279
280
      /*
281
       * Backing up from subplan EOF, so re-fetch previous tuple; there
282
       * should be one!  Note previous tuple must be in window.
283
       */
284
0
      slot = ExecProcNode(outerPlan);
285
0
      if (TupIsNull(slot))
286
0
        elog(ERROR, "LIMIT subplan failed to run backwards");
287
0
      node->subSlot = slot;
288
0
      node->lstate = LIMIT_INWINDOW;
289
      /* position does not change 'cause we didn't advance it before */
290
0
      break;
291
292
0
    case LIMIT_WINDOWEND:
293
0
      if (ScanDirectionIsForward(direction))
294
0
        return NULL;
295
296
      /*
297
       * We already past one position to detect ties so re-fetch
298
       * previous tuple; there should be one!  Note previous tuple must
299
       * be in window.
300
       */
301
0
      if (node->limitOption == LIMIT_OPTION_WITH_TIES)
302
0
      {
303
0
        slot = ExecProcNode(outerPlan);
304
0
        if (TupIsNull(slot))
305
0
          elog(ERROR, "LIMIT subplan failed to run backwards");
306
0
        node->subSlot = slot;
307
0
        node->lstate = LIMIT_INWINDOW;
308
0
      }
309
0
      else
310
0
      {
311
        /*
312
         * Backing up from window end: simply re-return the last tuple
313
         * fetched from the subplan.
314
         */
315
0
        slot = node->subSlot;
316
0
        node->lstate = LIMIT_INWINDOW;
317
        /* position does not change 'cause we didn't advance it before */
318
0
      }
319
0
      break;
320
321
0
    case LIMIT_WINDOWSTART:
322
0
      if (!ScanDirectionIsForward(direction))
323
0
        return NULL;
324
325
      /*
326
       * Advancing after having backed off window start: simply
327
       * re-return the last tuple fetched from the subplan.
328
       */
329
0
      slot = node->subSlot;
330
0
      node->lstate = LIMIT_INWINDOW;
331
      /* position does not change 'cause we didn't change it before */
332
0
      break;
333
334
0
    default:
335
0
      elog(ERROR, "impossible LIMIT state: %d",
336
0
         (int) node->lstate);
337
0
      slot = NULL;   /* keep compiler quiet */
338
0
      break;
339
0
  }
340
341
  /* Return the current tuple */
342
0
  Assert(!TupIsNull(slot));
343
344
0
  return slot;
345
0
}
346
347
/*
348
 * Evaluate the limit/offset expressions --- done at startup or rescan.
349
 *
350
 * This is also a handy place to reset the current-position state info.
351
 */
352
static void
353
recompute_limits(LimitState *node)
354
0
{
355
0
  ExprContext *econtext = node->ps.ps_ExprContext;
356
0
  Datum   val;
357
0
  bool    isNull;
358
359
0
  if (node->limitOffset)
360
0
  {
361
0
    val = ExecEvalExprSwitchContext(node->limitOffset,
362
0
                    econtext,
363
0
                    &isNull);
364
    /* Interpret NULL offset as no offset */
365
0
    if (isNull)
366
0
      node->offset = 0;
367
0
    else
368
0
    {
369
0
      node->offset = DatumGetInt64(val);
370
0
      if (node->offset < 0)
371
0
        ereport(ERROR,
372
0
            (errcode(ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE),
373
0
             errmsg("OFFSET must not be negative")));
374
0
    }
375
0
  }
376
0
  else
377
0
  {
378
    /* No OFFSET supplied */
379
0
    node->offset = 0;
380
0
  }
381
382
0
  if (node->limitCount)
383
0
  {
384
0
    val = ExecEvalExprSwitchContext(node->limitCount,
385
0
                    econtext,
386
0
                    &isNull);
387
    /* Interpret NULL count as no count (LIMIT ALL) */
388
0
    if (isNull)
389
0
    {
390
0
      node->count = 0;
391
0
      node->noCount = true;
392
0
    }
393
0
    else
394
0
    {
395
0
      node->count = DatumGetInt64(val);
396
0
      if (node->count < 0)
397
0
        ereport(ERROR,
398
0
            (errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
399
0
             errmsg("LIMIT must not be negative")));
400
0
      node->noCount = false;
401
0
    }
402
0
  }
403
0
  else
404
0
  {
405
    /* No COUNT supplied */
406
0
    node->count = 0;
407
0
    node->noCount = true;
408
0
  }
409
410
  /* Reset position to start-of-scan */
411
0
  node->position = 0;
412
0
  node->subSlot = NULL;
413
414
  /* Set state-machine state */
415
0
  node->lstate = LIMIT_RESCAN;
416
417
  /*
418
   * Notify child node about limit.  Note: think not to "optimize" by
419
   * skipping ExecSetTupleBound if compute_tuples_needed returns < 0.  We
420
   * must update the child node anyway, in case this is a rescan and the
421
   * previous time we got a different result.
422
   */
423
0
  ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
424
0
}
425
426
/*
427
 * Compute the maximum number of tuples needed to satisfy this Limit node.
428
 * Return a negative value if there is not a determinable limit.
429
 */
430
static int64
431
compute_tuples_needed(LimitState *node)
432
0
{
433
0
  if ((node->noCount) || (node->limitOption == LIMIT_OPTION_WITH_TIES))
434
0
    return -1;
435
  /* Note: if this overflows, we'll return a negative value, which is OK */
436
0
  return node->count + node->offset;
437
0
}
438
439
/* ----------------------------------------------------------------
440
 *    ExecInitLimit
441
 *
442
 *    This initializes the limit node state structures and
443
 *    the node's subplan.
444
 * ----------------------------------------------------------------
445
 */
446
LimitState *
447
ExecInitLimit(Limit *node, EState *estate, int eflags)
448
0
{
449
0
  LimitState *limitstate;
450
0
  Plan     *outerPlan;
451
452
  /* check for unsupported flags */
453
0
  Assert(!(eflags & EXEC_FLAG_MARK));
454
455
  /*
456
   * create state structure
457
   */
458
0
  limitstate = makeNode(LimitState);
459
0
  limitstate->ps.plan = (Plan *) node;
460
0
  limitstate->ps.state = estate;
461
0
  limitstate->ps.ExecProcNode = ExecLimit;
462
463
0
  limitstate->lstate = LIMIT_INITIAL;
464
465
  /*
466
   * Miscellaneous initialization
467
   *
468
   * Limit nodes never call ExecQual or ExecProject, but they need an
469
   * exprcontext anyway to evaluate the limit/offset parameters in.
470
   */
471
0
  ExecAssignExprContext(estate, &limitstate->ps);
472
473
  /*
474
   * initialize outer plan
475
   */
476
0
  outerPlan = outerPlan(node);
477
0
  outerPlanState(limitstate) = ExecInitNode(outerPlan, estate, eflags);
478
479
  /*
480
   * initialize child expressions
481
   */
482
0
  limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
483
0
                       (PlanState *) limitstate);
484
0
  limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
485
0
                      (PlanState *) limitstate);
486
0
  limitstate->limitOption = node->limitOption;
487
488
  /*
489
   * Initialize result type.
490
   */
491
0
  ExecInitResultTypeTL(&limitstate->ps);
492
493
0
  limitstate->ps.resultopsset = true;
494
0
  limitstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(limitstate),
495
0
                          &limitstate->ps.resultopsfixed);
496
497
  /*
498
   * limit nodes do no projections, so initialize projection info for this
499
   * node appropriately
500
   */
501
0
  limitstate->ps.ps_ProjInfo = NULL;
502
503
  /*
504
   * Initialize the equality evaluation, to detect ties.
505
   */
506
0
  if (node->limitOption == LIMIT_OPTION_WITH_TIES)
507
0
  {
508
0
    TupleDesc desc;
509
0
    const TupleTableSlotOps *ops;
510
511
0
    desc = ExecGetResultType(outerPlanState(limitstate));
512
0
    ops = ExecGetResultSlotOps(outerPlanState(limitstate), NULL);
513
514
0
    limitstate->last_slot = ExecInitExtraTupleSlot(estate, desc, ops);
515
0
    limitstate->eqfunction = execTuplesMatchPrepare(desc,
516
0
                            node->uniqNumCols,
517
0
                            node->uniqColIdx,
518
0
                            node->uniqOperators,
519
0
                            node->uniqCollations,
520
0
                            &limitstate->ps);
521
0
  }
522
523
0
  return limitstate;
524
0
}
525
526
/* ----------------------------------------------------------------
527
 *    ExecEndLimit
528
 *
529
 *    This shuts down the subplan and frees resources allocated
530
 *    to this node.
531
 * ----------------------------------------------------------------
532
 */
533
void
534
ExecEndLimit(LimitState *node)
535
0
{
536
0
  ExecEndNode(outerPlanState(node));
537
0
}
538
539
540
void
541
ExecReScanLimit(LimitState *node)
542
0
{
543
0
  PlanState  *outerPlan = outerPlanState(node);
544
545
  /*
546
   * Recompute limit/offset in case parameters changed, and reset the state
547
   * machine.  We must do this before rescanning our child node, in case
548
   * it's a Sort that we are passing the parameters down to.
549
   */
550
0
  recompute_limits(node);
551
552
  /*
553
   * if chgParam of subnode is not null then plan will be re-scanned by
554
   * first ExecProcNode.
555
   */
556
0
  if (outerPlan->chgParam == NULL)
557
0
    ExecReScan(outerPlan);
558
0
}