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