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