Coverage Report

Created: 2026-09-28 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/windowfuncs.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * windowfuncs.c
4
 *    Standard window functions defined in SQL spec.
5
 *
6
 * Portions Copyright (c) 2000-2026, PostgreSQL Global Development Group
7
 *
8
 *
9
 * IDENTIFICATION
10
 *    src/backend/utils/adt/windowfuncs.c
11
 *
12
 *-------------------------------------------------------------------------
13
 */
14
#include "postgres.h"
15
16
#include "nodes/parsenodes.h"
17
#include "nodes/supportnodes.h"
18
#include "utils/fmgrprotos.h"
19
#include "windowapi.h"
20
21
/*
22
 * ranking process information
23
 */
24
typedef struct rank_context
25
{
26
  int64   rank;     /* current rank */
27
} rank_context;
28
29
/*
30
 * ntile process information
31
 */
32
typedef struct
33
{
34
  int32   ntile;      /* current result */
35
  int64   rows_per_bucket;  /* row number of current bucket */
36
  int64   boundary;   /* how many rows should be in the bucket */
37
  int64   remainder;    /* (total rows) % (bucket num) */
38
} ntile_context;
39
40
static bool rank_up(WindowObject winobj);
41
static Datum leadlag_common(FunctionCallInfo fcinfo,
42
              bool forward, bool withoffset, bool withdefault);
43
44
45
/*
46
 * utility routine for *_rank functions.
47
 */
48
static bool
49
rank_up(WindowObject winobj)
50
0
{
51
0
  bool    up = false;    /* should rank increase? */
52
0
  int64   curpos = WinGetCurrentPosition(winobj);
53
0
  rank_context *context;
54
55
0
  context = (rank_context *)
56
0
    WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
57
58
0
  if (context->rank == 0)
59
0
  {
60
    /* first call: rank of first row is always 1 */
61
0
    Assert(curpos == 0);
62
0
    context->rank = 1;
63
0
  }
64
0
  else
65
0
  {
66
0
    Assert(curpos > 0);
67
    /* do current and prior tuples match by ORDER BY clause? */
68
0
    if (!WinRowsArePeers(winobj, curpos - 1, curpos))
69
0
      up = true;
70
0
  }
71
72
  /* We can advance the mark, but only *after* access to prior row */
73
0
  WinSetMarkPosition(winobj, curpos);
74
75
0
  return up;
76
0
}
77
78
79
/*
80
 * row_number
81
 * just increment up from 1 until current partition finishes.
82
 */
83
Datum
84
window_row_number(PG_FUNCTION_ARGS)
85
0
{
86
0
  WindowObject winobj = PG_WINDOW_OBJECT();
87
0
  int64   curpos = WinGetCurrentPosition(winobj);
88
89
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
90
0
  WinSetMarkPosition(winobj, curpos);
91
0
  PG_RETURN_INT64(curpos + 1);
92
0
}
93
94
/*
95
 * window_row_number_support
96
 *    prosupport function for window_row_number()
97
 */
98
Datum
99
window_row_number_support(PG_FUNCTION_ARGS)
100
0
{
101
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
102
103
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
104
0
  {
105
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
106
107
    /* row_number() is monotonically increasing */
108
0
    req->monotonic = MONOTONICFUNC_INCREASING;
109
0
    PG_RETURN_POINTER(req);
110
0
  }
111
112
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
113
0
  {
114
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
115
116
    /*
117
     * The frame options can always become "ROWS BETWEEN UNBOUNDED
118
     * PRECEDING AND CURRENT ROW".  row_number() always just increments by
119
     * 1 with each row in the partition.  Using ROWS instead of RANGE
120
     * saves effort checking peer rows during execution.
121
     */
122
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
123
0
               FRAMEOPTION_ROWS |
124
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
125
0
               FRAMEOPTION_END_CURRENT_ROW);
126
127
0
    PG_RETURN_POINTER(req);
128
0
  }
129
130
0
  PG_RETURN_POINTER(NULL);
131
0
}
132
133
/*
134
 * rank
135
 * Rank changes when key columns change.
136
 * The new rank number is the current row number.
137
 */
138
Datum
139
window_rank(PG_FUNCTION_ARGS)
140
0
{
141
0
  WindowObject winobj = PG_WINDOW_OBJECT();
142
0
  rank_context *context;
143
0
  bool    up;
144
145
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
146
0
  up = rank_up(winobj);
147
0
  context = (rank_context *)
148
0
    WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
149
0
  if (up)
150
0
    context->rank = WinGetCurrentPosition(winobj) + 1;
151
152
0
  PG_RETURN_INT64(context->rank);
153
0
}
154
155
/*
156
 * window_rank_support
157
 *    prosupport function for window_rank()
158
 */
159
Datum
160
window_rank_support(PG_FUNCTION_ARGS)
161
0
{
162
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
163
164
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
165
0
  {
166
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
167
168
    /* rank() is monotonically increasing */
169
0
    req->monotonic = MONOTONICFUNC_INCREASING;
170
0
    PG_RETURN_POINTER(req);
171
0
  }
172
173
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
174
0
  {
175
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
176
177
    /*
178
     * rank() is coded in such a way that it returns "(COUNT (*) OVER
179
     * (<opt> RANGE UNBOUNDED PRECEDING) - COUNT (*) OVER (<opt> RANGE
180
     * CURRENT ROW) + 1)" regardless of the frame options.  We'll set the
181
     * frame options to "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
182
     * so they agree with what window_row_number_support() optimized the
183
     * frame options to be.  Using ROWS instead of RANGE saves from doing
184
     * peer row checks during execution.
185
     */
186
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
187
0
               FRAMEOPTION_ROWS |
188
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
189
0
               FRAMEOPTION_END_CURRENT_ROW);
190
191
0
    PG_RETURN_POINTER(req);
192
0
  }
193
194
0
  PG_RETURN_POINTER(NULL);
195
0
}
196
197
/*
198
 * dense_rank
199
 * Rank increases by 1 when key columns change.
200
 */
201
Datum
202
window_dense_rank(PG_FUNCTION_ARGS)
203
0
{
204
0
  WindowObject winobj = PG_WINDOW_OBJECT();
205
0
  rank_context *context;
206
0
  bool    up;
207
208
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
209
0
  up = rank_up(winobj);
210
0
  context = (rank_context *)
211
0
    WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
212
0
  if (up)
213
0
    context->rank++;
214
215
0
  PG_RETURN_INT64(context->rank);
216
0
}
217
218
/*
219
 * window_dense_rank_support
220
 *    prosupport function for window_dense_rank()
221
 */
222
Datum
223
window_dense_rank_support(PG_FUNCTION_ARGS)
224
0
{
225
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
226
227
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
228
0
  {
229
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
230
231
    /* dense_rank() is monotonically increasing */
232
0
    req->monotonic = MONOTONICFUNC_INCREASING;
233
0
    PG_RETURN_POINTER(req);
234
0
  }
235
236
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
237
0
  {
238
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
239
240
    /*
241
     * dense_rank() is unaffected by the frame options.  Here we set the
242
     * frame options to match what's done in row_number's support
243
     * function.  Using ROWS instead of RANGE (the default) saves the
244
     * executor from having to check for peer rows.
245
     */
246
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
247
0
               FRAMEOPTION_ROWS |
248
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
249
0
               FRAMEOPTION_END_CURRENT_ROW);
250
251
0
    PG_RETURN_POINTER(req);
252
0
  }
253
254
0
  PG_RETURN_POINTER(NULL);
255
0
}
256
257
/*
258
 * percent_rank
259
 * return fraction between 0 and 1 inclusive,
260
 * which is described as (RK - 1) / (NR - 1), where RK is the current row's
261
 * rank and NR is the total number of rows, per spec.
262
 */
263
Datum
264
window_percent_rank(PG_FUNCTION_ARGS)
265
0
{
266
0
  WindowObject winobj = PG_WINDOW_OBJECT();
267
0
  rank_context *context;
268
0
  bool    up;
269
0
  int64   totalrows = WinGetPartitionRowCount(winobj);
270
271
0
  Assert(totalrows > 0);
272
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
273
274
0
  up = rank_up(winobj);
275
0
  context = (rank_context *)
276
0
    WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
277
0
  if (up)
278
0
    context->rank = WinGetCurrentPosition(winobj) + 1;
279
280
  /* return zero if there's only one row, per spec */
281
0
  if (totalrows <= 1)
282
0
    PG_RETURN_FLOAT8(0.0);
283
284
0
  PG_RETURN_FLOAT8((float8) (context->rank - 1) / (float8) (totalrows - 1));
285
0
}
286
287
/*
288
 * window_percent_rank_support
289
 *    prosupport function for window_percent_rank()
290
 */
291
Datum
292
window_percent_rank_support(PG_FUNCTION_ARGS)
293
0
{
294
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
295
296
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
297
0
  {
298
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
299
300
    /* percent_rank() is monotonically increasing */
301
0
    req->monotonic = MONOTONICFUNC_INCREASING;
302
0
    PG_RETURN_POINTER(req);
303
0
  }
304
305
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
306
0
  {
307
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
308
309
    /*
310
     * percent_rank() is unaffected by the frame options.  Here we set the
311
     * frame options to match what's done in row_number's support
312
     * function.  Using ROWS instead of RANGE (the default) saves the
313
     * executor from having to check for peer rows.
314
     */
315
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
316
0
               FRAMEOPTION_ROWS |
317
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
318
0
               FRAMEOPTION_END_CURRENT_ROW);
319
320
0
    PG_RETURN_POINTER(req);
321
0
  }
322
323
0
  PG_RETURN_POINTER(NULL);
324
0
}
325
326
327
/*
328
 * cume_dist
329
 * return fraction between 0 and 1 inclusive,
330
 * which is described as NP / NR, where NP is the number of rows preceding or
331
 * peers to the current row, and NR is the total number of rows, per spec.
332
 */
333
Datum
334
window_cume_dist(PG_FUNCTION_ARGS)
335
0
{
336
0
  WindowObject winobj = PG_WINDOW_OBJECT();
337
0
  rank_context *context;
338
0
  bool    up;
339
0
  int64   totalrows = WinGetPartitionRowCount(winobj);
340
341
0
  Assert(totalrows > 0);
342
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
343
344
0
  up = rank_up(winobj);
345
0
  context = (rank_context *)
346
0
    WinGetPartitionLocalMemory(winobj, sizeof(rank_context));
347
0
  if (up || context->rank == 1)
348
0
  {
349
    /*
350
     * The current row is not peer to prior row or is just the first, so
351
     * count up the number of rows that are peer to the current.
352
     */
353
0
    int64   row;
354
355
0
    context->rank = WinGetCurrentPosition(winobj) + 1;
356
357
    /*
358
     * start from current + 1
359
     */
360
0
    for (row = context->rank; row < totalrows; row++)
361
0
    {
362
0
      if (!WinRowsArePeers(winobj, row - 1, row))
363
0
        break;
364
0
      context->rank++;
365
0
    }
366
0
  }
367
368
0
  PG_RETURN_FLOAT8((float8) context->rank / (float8) totalrows);
369
0
}
370
371
/*
372
 * window_cume_dist_support
373
 *    prosupport function for window_cume_dist()
374
 */
375
Datum
376
window_cume_dist_support(PG_FUNCTION_ARGS)
377
0
{
378
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
379
380
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
381
0
  {
382
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
383
384
    /* cume_dist() is monotonically increasing */
385
0
    req->monotonic = MONOTONICFUNC_INCREASING;
386
0
    PG_RETURN_POINTER(req);
387
0
  }
388
389
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
390
0
  {
391
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
392
393
    /*
394
     * cume_dist() is unaffected by the frame options.  Here we set the
395
     * frame options to match what's done in row_number's support
396
     * function.  Using ROWS instead of RANGE (the default) saves the
397
     * executor from having to check for peer rows.
398
     */
399
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
400
0
               FRAMEOPTION_ROWS |
401
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
402
0
               FRAMEOPTION_END_CURRENT_ROW);
403
404
0
    PG_RETURN_POINTER(req);
405
0
  }
406
407
0
  PG_RETURN_POINTER(NULL);
408
0
}
409
410
/*
411
 * ntile
412
 * compute an exact numeric value with scale 0 (zero),
413
 * ranging from 1 (one) to n, per spec.
414
 */
415
Datum
416
window_ntile(PG_FUNCTION_ARGS)
417
0
{
418
0
  WindowObject winobj = PG_WINDOW_OBJECT();
419
0
  ntile_context *context;
420
421
0
  WinCheckAndInitializeNullTreatment(winobj, false, fcinfo);
422
0
  context = (ntile_context *)
423
0
    WinGetPartitionLocalMemory(winobj, sizeof(ntile_context));
424
425
0
  if (context->ntile == 0)
426
0
  {
427
    /* first call */
428
0
    int64   total;
429
0
    int32   nbuckets;
430
0
    bool    isnull;
431
432
0
    total = WinGetPartitionRowCount(winobj);
433
0
    nbuckets = DatumGetInt32(WinGetFuncArgCurrent(winobj, 0, &isnull));
434
435
    /*
436
     * per spec: If NT is the null value, then the result is the null
437
     * value.
438
     */
439
0
    if (isnull)
440
0
      PG_RETURN_NULL();
441
442
    /*
443
     * per spec: If NT is less than or equal to 0 (zero), then an
444
     * exception condition is raised.
445
     */
446
0
    if (nbuckets <= 0)
447
0
      ereport(ERROR,
448
0
          (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTILE),
449
0
           errmsg("argument of ntile must be greater than zero")));
450
451
0
    context->ntile = 1;
452
0
    context->rows_per_bucket = 0;
453
0
    context->boundary = total / nbuckets;
454
0
    if (context->boundary <= 0)
455
0
      context->boundary = 1;
456
0
    else
457
0
    {
458
      /*
459
       * If the total number is not divisible, add 1 row to leading
460
       * buckets.
461
       */
462
0
      context->remainder = total % nbuckets;
463
0
      if (context->remainder != 0)
464
0
        context->boundary++;
465
0
    }
466
0
  }
467
468
0
  context->rows_per_bucket++;
469
0
  if (context->boundary < context->rows_per_bucket)
470
0
  {
471
    /* ntile up */
472
0
    if (context->remainder != 0 && context->ntile == context->remainder)
473
0
    {
474
0
      context->remainder = 0;
475
0
      context->boundary -= 1;
476
0
    }
477
0
    context->ntile += 1;
478
0
    context->rows_per_bucket = 1;
479
0
  }
480
481
0
  PG_RETURN_INT32(context->ntile);
482
0
}
483
484
/*
485
 * window_ntile_support
486
 *    prosupport function for window_ntile()
487
 */
488
Datum
489
window_ntile_support(PG_FUNCTION_ARGS)
490
0
{
491
0
  Node     *rawreq = (Node *) PG_GETARG_POINTER(0);
492
493
0
  if (IsA(rawreq, SupportRequestWFuncMonotonic))
494
0
  {
495
0
    SupportRequestWFuncMonotonic *req = (SupportRequestWFuncMonotonic *) rawreq;
496
497
    /*
498
     * ntile() is monotonically increasing as the number of buckets cannot
499
     * change after the first call
500
     */
501
0
    req->monotonic = MONOTONICFUNC_INCREASING;
502
0
    PG_RETURN_POINTER(req);
503
0
  }
504
505
0
  if (IsA(rawreq, SupportRequestOptimizeWindowClause))
506
0
  {
507
0
    SupportRequestOptimizeWindowClause *req = (SupportRequestOptimizeWindowClause *) rawreq;
508
509
    /*
510
     * ntile() is unaffected by the frame options.  Here we set the frame
511
     * options to match what's done in row_number's support function.
512
     * Using ROWS instead of RANGE (the default) saves the executor from
513
     * having to check for peer rows.
514
     */
515
0
    req->frameOptions = (FRAMEOPTION_NONDEFAULT |
516
0
               FRAMEOPTION_ROWS |
517
0
               FRAMEOPTION_START_UNBOUNDED_PRECEDING |
518
0
               FRAMEOPTION_END_CURRENT_ROW);
519
520
0
    PG_RETURN_POINTER(req);
521
0
  }
522
523
0
  PG_RETURN_POINTER(NULL);
524
0
}
525
526
/*
527
 * leadlag_common
528
 * common operation of lead() and lag()
529
 * For lead() forward is true, whereas for lag() it is false.
530
 * withoffset indicates we have an offset second argument.
531
 * withdefault indicates we have a default third argument.
532
 */
533
static Datum
534
leadlag_common(FunctionCallInfo fcinfo,
535
         bool forward, bool withoffset, bool withdefault)
536
0
{
537
0
  WindowObject winobj = PG_WINDOW_OBJECT();
538
0
  int32   offset;
539
0
  bool    const_offset;
540
0
  Datum   result;
541
0
  bool    isnull;
542
0
  bool    isout;
543
544
0
  WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
545
0
  if (withoffset)
546
0
  {
547
0
    offset = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
548
0
    if (isnull)
549
0
      PG_RETURN_NULL();
550
0
    const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
551
0
  }
552
0
  else
553
0
  {
554
0
    offset = 1;
555
0
    const_offset = true;
556
0
  }
557
558
0
  result = WinGetFuncArgInPartition(winobj, 0,
559
0
                    (forward ? offset : -offset),
560
0
                    WINDOW_SEEK_CURRENT,
561
0
                    const_offset,
562
0
                    &isnull, &isout);
563
564
0
  if (isout)
565
0
  {
566
    /*
567
     * target row is out of the partition; supply default value if
568
     * provided.  otherwise it'll stay NULL
569
     */
570
0
    if (withdefault)
571
0
      result = WinGetFuncArgCurrent(winobj, 2, &isnull);
572
0
  }
573
574
0
  if (isnull)
575
0
    PG_RETURN_NULL();
576
577
0
  PG_RETURN_DATUM(result);
578
0
}
579
580
/*
581
 * lag
582
 * returns the value of VE evaluated on a row that is 1
583
 * row before the current row within a partition,
584
 * per spec.
585
 */
586
Datum
587
window_lag(PG_FUNCTION_ARGS)
588
0
{
589
0
  return leadlag_common(fcinfo, false, false, false);
590
0
}
591
592
/*
593
 * lag_with_offset
594
 * returns the value of VE evaluated on a row that is OFFSET
595
 * rows before the current row within a partition,
596
 * per spec.
597
 */
598
Datum
599
window_lag_with_offset(PG_FUNCTION_ARGS)
600
0
{
601
0
  return leadlag_common(fcinfo, false, true, false);
602
0
}
603
604
/*
605
 * lag_with_offset_and_default
606
 * same as lag_with_offset but accepts default value
607
 * as its third argument.
608
 */
609
Datum
610
window_lag_with_offset_and_default(PG_FUNCTION_ARGS)
611
0
{
612
0
  return leadlag_common(fcinfo, false, true, true);
613
0
}
614
615
/*
616
 * lead
617
 * returns the value of VE evaluated on a row that is 1
618
 * row after the current row within a partition,
619
 * per spec.
620
 */
621
Datum
622
window_lead(PG_FUNCTION_ARGS)
623
0
{
624
0
  return leadlag_common(fcinfo, true, false, false);
625
0
}
626
627
/*
628
 * lead_with_offset
629
 * returns the value of VE evaluated on a row that is OFFSET
630
 * number of rows after the current row within a partition,
631
 * per spec.
632
 */
633
Datum
634
window_lead_with_offset(PG_FUNCTION_ARGS)
635
0
{
636
0
  return leadlag_common(fcinfo, true, true, false);
637
0
}
638
639
/*
640
 * lead_with_offset_and_default
641
 * same as lead_with_offset but accepts default value
642
 * as its third argument.
643
 */
644
Datum
645
window_lead_with_offset_and_default(PG_FUNCTION_ARGS)
646
0
{
647
0
  return leadlag_common(fcinfo, true, true, true);
648
0
}
649
650
/*
651
 * first_value
652
 * return the value of VE evaluated on the first row of the
653
 * window frame, per spec.
654
 */
655
Datum
656
window_first_value(PG_FUNCTION_ARGS)
657
0
{
658
0
  WindowObject winobj = PG_WINDOW_OBJECT();
659
0
  Datum   result;
660
0
  bool    isnull;
661
662
0
  WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
663
0
  result = WinGetFuncArgInFrame(winobj, 0,
664
0
                  0, WINDOW_SEEK_HEAD, true,
665
0
                  &isnull, NULL);
666
0
  if (isnull)
667
0
    PG_RETURN_NULL();
668
669
0
  PG_RETURN_DATUM(result);
670
0
}
671
672
/*
673
 * last_value
674
 * return the value of VE evaluated on the last row of the
675
 * window frame, per spec.
676
 */
677
Datum
678
window_last_value(PG_FUNCTION_ARGS)
679
0
{
680
0
  WindowObject winobj = PG_WINDOW_OBJECT();
681
0
  Datum   result;
682
0
  bool    isnull;
683
684
0
  WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
685
0
  result = WinGetFuncArgInFrame(winobj, 0,
686
0
                  0, WINDOW_SEEK_TAIL, true,
687
0
                  &isnull, NULL);
688
0
  if (isnull)
689
0
    PG_RETURN_NULL();
690
691
0
  PG_RETURN_DATUM(result);
692
0
}
693
694
/*
695
 * nth_value
696
 * return the value of VE evaluated on the n-th row from the first
697
 * row of the window frame, per spec.
698
 */
699
Datum
700
window_nth_value(PG_FUNCTION_ARGS)
701
0
{
702
0
  WindowObject winobj = PG_WINDOW_OBJECT();
703
0
  bool    const_offset;
704
0
  Datum   result;
705
0
  bool    isnull;
706
0
  int32   nth;
707
708
0
  WinCheckAndInitializeNullTreatment(winobj, true, fcinfo);
709
0
  nth = DatumGetInt32(WinGetFuncArgCurrent(winobj, 1, &isnull));
710
0
  if (isnull)
711
0
    PG_RETURN_NULL();
712
0
  const_offset = get_fn_expr_arg_stable(fcinfo->flinfo, 1);
713
714
0
  if (nth <= 0)
715
0
    ereport(ERROR,
716
0
        (errcode(ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE),
717
0
         errmsg("argument of nth_value must be greater than zero")));
718
719
0
  result = WinGetFuncArgInFrame(winobj, 0,
720
0
                  nth - 1, WINDOW_SEEK_HEAD, const_offset,
721
0
                  &isnull, NULL);
722
0
  if (isnull)
723
0
    PG_RETURN_NULL();
724
725
0
  PG_RETURN_DATUM(result);
726
0
}